# uni-module-common

uni-module-common is a private library.

## Installation

Use the package manager [npm](https://www.npmjs.com/) to install uni-module-common.

```bash
npm install uni-module-common
```

## Example usage

```javascript
import { MultilineTextField } from 'uni-module-common'

render() {
  return (
    <MultilineTextField label="MyMultiLine" value="Lorem Ipsum">
    </MultilineTextField>
  );
}
```

## Components
- [Action](#action)
- [ActionBookmark](#actionbookmark)
- [ActionCell](#actioncell)
- [ActionDetail](#actiondetail)
- [ActionFileInput](#actionfileinput)
- [ActionMenu](#actionmenu)
- [ActionSelect](#actionselect)
- [ActionSeparator](#actionseparator)
- [AlertBox](#alertbox)
- [AppLink](#applink)
- [BodyCell](#bodycell)
- [CheckBox](#checkbox)
- [ClearableInput](#clearableinput)
- [CollapseCell](#collapsecell)
- [Color](#color)
- [ColorSelect](#colorselect)
- [ColumnFilter](#columnfilter)
- [ColumnFilterDate](#columnfilterdate)
- [ColumnFilterEnum](#columnfilterenum)
- [ColumnFilterNumber](#columnfilternumber)
- [ColumnFilterText](#columnfiltertext)
- [ConfirmBox](#confirmbox)
- [DataGrid](#datagrid)
- [DateCell](#datecell)
- [DateField](#datefield)
- [DateInput](#dateinput)
- [Dropdown](#dropdown)
- [DropdownExtended](#dropdownextended)
- [DropdownTab](#dropdowntab)
- [EditRelationControl](#editrelationcontrol)
- [ExtendedCheckBox](#extendedcheckbox)
- [Field](#field)
- [FileImage](#fileimage)
- [FileInput](#fileinput)
- [FulltextInput](#fulltextinput)
- [FurtherDetailList](#furtherdetaillist)
- [GridFulltextSearch](#gridfulltextsearch)
- [HeaderActions](#headeractions)
- [HeaderCell](#headercell)
- [HierarchicalSelectField](#hierarchicalselectfield)
- [Icon](#icon)
- [Image](#image)
- [MultilineTextField](#multilinetextfield)
- [MultilineTextFieldCountIndicator](#multilinetextfieldcountindicator)
- [NavIcon](#navicon)
- [NumberCell](#numbercell)
- [NumberField](#numberfield)
- [ObjectCard](#objectcard)
- [OrderableList](#orderablelist)
- [Overlay](#overlay)
- [Paginator](#paginator)
- [ProgressBar](#progressbar)
- [Property](#property)
- [PropertyAppLink](#propertyapplink)
- [RadioButton](#radiobutton)
- [RelationControl](#relationcontrol)
- [RoundSwitch](#roundswitch)
- [RowToolsCell](#rowtoolscell)
- [SearchField](#searchfield)
- [searchFieldSelect](#searchfieldselect)
- [SearchSelect](#searchselect)
- [SelectField](#selectfield)
- [SelectorCell](#selectorcell)
- [SelectorHeaderCell](#selectorheadercell)
- [Spinner](#spinner)
- [TextCell](#textcell)
- [TextField](#textfield)
- [TextHighlight](#texthighlight)
- [ToolsButton](#toolsbutton)
- [TriconField](#triconfield)

## Action
**Description**: Action icon with a hover effect based on application theme. Can be used either by itself or as a child of an action menu (three dots menu).

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/action1.png?raw=true)

**Props**:
```
Action.propTypes = {
  selected: PropTypes.bool,
  disabled: PropTypes.bool,
  label: PropTypes.bool,
  icon: PropTypes.string,
  onClick: PropTypes.func,
  children: PropTypes.any,
  condition: PropTypes.any,
  toolTip: PropTypes.string,
  className: PropTypes.string,
  title: PropTypes.string,
  style: PropTypes.object
}
```
- **selected** - if true, an icon is colored all the time (and not just if hovered), which can be useful for toggle-like effect
- **disabled** - if true, action is visible, but grayed-out and unable to be clicked
- **label** - if true, a text of the action will be showed
- **icon** - name of the icon, see [Icon](#icon) for possible choices
- **onClick**
- **children** - voluntary collection of html elements to be displayed inside an action
- **condition** - boolean or function value, that can indicate if the action should be displayed at all
- **toolTip**
- **className** - additional class that appends the default "Action" class
- **title** - text of the action, useful mainly in the action menu ("Edit data" text in the image above)
- **style** - additional style object to be applied to the "Action" element

**Usage**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                <div className="Panel-toolbarContent">
                    <Action onClick={this.handleShowItemMediaClick.bind(this)} 
                        disabled={false} 
                        title="Media" 
                        icon="folder" />
                </div>
                <div className="Panel-toolbarActions">
                    <ActionMenu hybridMenu={false} 
                        anchor="right" 
                        translator={this.props.appInterface.tree.select('workspace', 'localization', 'root').get()}>
                        <Action title="Edit" 
                            condition={canUserEditItem} 
                            onClick={this.handleEditItemClick.bind(this)} 
                            icon="edit" />
                    </ActionMenu>
                </div>
            </div>
            <div className="Panel-content">
                ...
            </div>
        </div>
    )
}
```
[Back](#components)

#### ActionBookmark
**Description**: Specialized kind of action for bookmarking certain objects.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/actionbookmark1.png?raw=true)

**Props**:
```
ActionBookmark.propTypes = {
  type: PropTypes.number.isRequired,
  data: PropTypes.object.isRequired,
  bookmarks: PropTypes.any,
  localization: PropTypes.object.isRequired,
  bookmarkActions: PropTypes.shape({
    bookmarked: PropTypes.func.isRequired,
    removeBookmark: PropTypes.func.isRequired,
    addBookmark: PropTypes.func.isRequired
  }).isRequired,
  refreshFunc: PropTypes.func
}
```
- **type** - one of numeric values representing type of bookmarked object: FULLTEXT: 1, PARCEL: 2, ADDRESS: 3, SELECTION: 4, COORDINATES: 5, DRAWING: 6, GRID: 7, MODULE: 8. Recommended value is 1 (FULLTEXT) in most cases
- **data** - object to be bookmarked. It should have a unique *id* propery to be identified by
- **bookmarks** - object representing the bookmark cache
- **localization** - object with localized strings. At least *localization.bookmarks.tooltip* should be accessible
- **bookmarkActions** - functions to process the bookmark requests
- **refreshFunc** - function to be called after operations with bookmarks are finished

**Usage**:
``` javascript
render() {
    let localization = this.props.appInterface.tree.select('workspace', 'localization').get();
    let bookmarks = this.props.appInterface.tree.select('bookmarks').get();
    return (
        <div>
            <div className="Panel-toolbar">
                <div className="Panel-toolbarContent">
                    <ActionBookmark 
                            data={this.state.currentItem} 
                            type={1} 
                            bookmarks={bookmarks}
                            localization={localization} 
                            bookmarkActions={this.props.appInterface.actions}
                            refreshFunc={this.refreshFunction.bind(this)}
                        />
                </div>
                <div className="Panel-toolbarActions">
                    ...
                </div>
            </div>
            <div className="Panel-content">
                ...
            </div>
        </div>
    )
}
```
[Back](#components)

#### ActionCell
**Description**: Table cell for hyperlinks.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/actioncell1.png?raw=true)

**Props**:
```
ActionCell.propTypes = {
  data: PropTypes.object,
  column: PropTypes.string,
  router: PropTypes.object,
  grid: PropTypes.object,
  localization: PropTypes.object,
  onClick: PropTypes.func,
  relation: PropTypes.object,
  isActionCellDisabled: PropTypes.bool,
  getEntityNameFunction: PropTypes.func.isRequired,
  fetchEntityDescriptionFunction: PropTypes.func.isRequired,
  getAliasesFunction: PropTypes.func.isRequired,
  resolveActionFunction: PropTypes.func.isRequired,
  replaceParamsFunction: PropTypes.func.isRequired,
  gridFetchSpecsForEntityFunction: PropTypes.func.isRequired,
  appLinkTypes: PropTypes.shape({
    EXTERNAL_URI: PropTypes.string.isRequired,
    SHOW_GRID: PropTypes.string.isRequired,
    SHOW_MODULE: PropTypes.string.isRequired,
    SHOW_DETAIL: PropTypes.string.isRequired
  }),
  relationRoles: PropTypes.shape({
    CHILD: PropTypes.string.isRequired,
    PARENT: PropTypes.string.isRequired,
    MULTI: PropTypes.string.isRequired
  }),
  dataTypes: PropTypes.shape({
    BLOB: PropTypes.string.isRequired,
    CLOB: PropTypes.string.isRequired,
    DATE: PropTypes.string.isRequired,
    ENUM: PropTypes.string.isRequired,
    GEOM: PropTypes.string.isRequired,
    NUMBER: PropTypes.string.isRequired,
    UNKNOWN: PropTypes.string.isRequired,
    VARCHAR: PropTypes.string.isRequired,
    ACTION: PropTypes.string.isRequired,
  }),
  filterTypes: PropTypes.shape({
    EQUALS: PropTypes.string.isRequired,
    NOTEQUALS: PropTypes.string.isRequired,
    CONTAINS: PropTypes.string.isRequired,
    NOTCONTAINS: PropTypes.string.isRequired,
    GREATERTHAN: PropTypes.string.isRequired,
    GREATERTHANOREQUALS: PropTypes.string.isRequired,
    LESSERTHAN: PropTypes.string.isRequired,
    LESSERTHANOREQUALS: PropTypes.string.isRequired,
    BETWEEN: PropTypes.string.isRequired,
    ISNULL: PropTypes.string.isRequired,
    ISNOTNULL: PropTypes.string.isRequired,
    STARTSWITH: PropTypes.string.isRequired,
    ENDSWITH: PropTypes.string.isRequired,
    UNSPECIFIED: PropTypes.string.isRequired,
    IN: PropTypes.string.isRequired,
    NOTIN: PropTypes.string.isRequired
  }),
  isPhoneCompactView: PropTypes.bool
}
```
- **data** - object representing the table row. It must at least have a property corresponding to the *column* prop
- **column** - name of the value property of the data object
- **router** - router object for managing browser navigation. Value passed by the DataGrid parent object
- **grid** - object representing current grid settings. Value passed by the DataGrid parent object
- **localization** - object with localized strings. At least *localization.actionColumnDefaultText* should be accessible
- **onClick** - function to be called after the hyperlink click
- **relation** - object representing relation to other entity. Only for specialized usage inside twigis
- **isActionCellDisabled** - if true, the hyperlink is disabled
- **getEntityNameFunction** - function for parsing an entity name from a dataendpoint. Value passed by the DataGrid parent object
- **fetchEntityDescriptionFunction** - function for reading the description of a certain entity. Value passed by the DataGrid parent object
- **getAliasesFunction** - function for parsing captions for entity properties from entity description. Value passed by the DataGrid parent object
- **resolveActionFunction** - function for managing the default behavior of the action defined via twigis administration. Value passed by the DataGrid parent object
- **replaceParamsFunction** - helper function for *resolveActionFunction*. Value passed by the DataGrid parent object
- **gridFetchSpecsForEntityFunction** - function for reading the specs of a certain entity. Value passed by the DataGrid parent object
- **appLinkTypes**
- **relationRoles**
- **dataTypes**
- **filterTypes**
- **isPhoneCompactView** - if true, the component will be better mobile adapted

[Back](#components)

#### ActionDetail
**Description**: Action icon for opening detail from grid.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/actiondetail1.png?raw=true)

**Props**:
```
ActionDetail.propTypes = {
  icon: PropTypes.string,
  grid: PropTypes.object,
  router: PropTypes.object,
  data: PropTypes.object,
  onClick: PropTypes.func,
  onOpenEditPanel: PropTypes.func,
  onCloseEditPanel: PropTypes.func,
  localization: PropTypes.shape({
    showDetail: PropTypes.string.isRequired
  }).isRequired,
  openPanelFunction: PropTypes.func.isRequired,
  resolveActionFunction: PropTypes.func.isRequired,
  replaceParamsFunction: PropTypes.func.isRequired
}
```
- **icon** - name of the icon, see [Icon](#icon) for possible choices. Default value is 'info'
- **grid** - object representing current grid settings. Value passed by the DataGrid parent object
- **router** - router object for managing browser navigation. Value passed by the DataGrid parent object
- **data** - underlaying object (representation of the grid row)
- **onClick** - additional function to be called after an icon is clicked, to be performed right before the data detail is shown
- **onOpenEditPanel** - handler for managing DataGrid workflow. Value passed by the DataGrid parent object
- **onCloseEditPanel** - handler for managing DataGrid workflow. Value passed by the DataGrid parent object
- **localization** - object with localized strings
- **openPanelFunction** - function for opening new twigis panel. Value passed by the DataGrid parent object
- **resolveActionFunction** - function for managing the default behavior of the action defined via twigis administration. Value passed by the DataGrid parent object
- **replaceParamsFunction** - helper function for *resolveActionFunction*. Value passed by the DataGrid parent object

[Back](#components)

#### ActionFileInput
**Description**: Action icon with a built-in functionality for selecting files from the file system.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/actionfileinput1.png?raw=true)

**Props**:
```
ActionFileInput.propTypes = {
  selected: PropTypes.bool,
  disabled: PropTypes.bool,
  label: PropTypes.bool,
  icon: PropTypes.string,
  onFilesChosen: PropTypes.func,
  children: PropTypes.any,
  condition: PropTypes.any,
  toolTip: PropTypes.string,
  className: PropTypes.string,
  title: PropTypes.string
}
```
- **selected** - if true, an icon is colored all the time (and not just if hovered), which can be useful for toggle-like effect
- **disabled** - if true, action is visible, but grayed-out and unable to be clicked
- **label** - if true, a text of the action will be showed
- **icon** - name of the icon, see [Icon](#icon) for possible choices
- **onFilesChosen** - function to be called when files are chosen
- **children** - voluntary collection of html elements to be displayed inside an action
- **condition** - boolean or function value, that can indicate if the action should be displayed at all
- **toolTip**
- **className** - additional class that appends the default "Action" class
- **title** - text of the action, useful mainly in the action menu

**Usage**:
``` javascript
handleFilesChosenAction(files) {
    // TODO
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ActionFileInput
                    title={"Add file"}
                    condition={true}
                    disabled={false}
                    onFilesChosen={this.handleFilesChosenAction.bind(this)}
                    icon="add"
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### ActionMenu
**Description**: Dropdown three dots list of actions.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/actionmenu1.png?raw=true)

**Props**:
```
ActionMenu.propTypes = {
  opened: PropTypes.bool,
  onMenuToggle: PropTypes.func,
  children: PropTypes.any,
  anchor: PropTypes.string,
  orientation: PropTypes.string,
  hybridMenu: PropTypes.bool,
  onOpenOutsideWindow: PropTypes.func,
  translator: PropTypes.shape({
    menuToolTip: PropTypes.string.isRequired
  }).isRequired,
  isIconDisabled: PropTypes.bool
}
```
- **opened** - opened state can be influenced by this property or internally by user manipulation
- **onMenuToggle** - function to be called after dropdown menu is toggled
- **children** - items to be displayed in the dropdown. [Action](#action) or [ActionSeparator](#actionseparator) are recommended to be used
- **anchor** - 'right' or 'left' alignment of the menu. Default value is 'left'
- **orientation** - 'up' or 'down' popup orientation. Default is 'down'
- **hybridMenu** - if true and there is only one child, menu is displayed as a single [Action](#action)
- **onOpenOutsideWindow** - function for handling scrolling if the popup would be opened outside of window view
- **translator** - object with localized strings
- **isIconDisabled** - if true, three dots icon is grayed-out and unable to be clicked

**Usage**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                    <div className="Panel-toolbarContent">
                        ...
                    </div>
                    <div className="Panel-toolbarActions">
                        <ActionMenu hybridMenu={false} 
                            anchor="right" 
                            translator={this.props.appInterface.tree.select('workspace', 'localization', 'root').get()}>
                            <Action title={"Edit"} 
                                condition={canUserEdit} 
                                onClick={this.handleEditItemClick.bind(this)} 
                                icon="edit" />
                            <Action title={"Delete"} 
                                condition={canUserDelete}
                                onClick={this.handleDeleteItemClick.bind(this)} 
                                icon="delete" />
                            <ActionSeparator/>
                            <Action title={"Add"} 
                                condition={canUserAddItem}
                                onClick={this.handleAddItemClick.bind(this)} 
                                icon="add" />
                        </ActionMenu>
                    </div>
                </div>
            <div className="Panel-content">
                ...
            </div>
        </div>
    )
}
```
[Back](#components)

#### ActionSelect
**Description**: Action icon for showing an item in a map.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/actionselect1.png?raw=true)

**Props**:
```
ActionSelect.propTypes = {
  data: PropTypes.any,
  type: PropTypes.any,
  onClick: PropTypes.func,
  localization: PropTypes.shape({
    selectTitle: PropTypes.string.isRequired
  }).isRequired,
  hidePanelsFunction: PropTypes.func.isRequired,
  setFixedPanelsFunction: PropTypes.func.isRequired,
  setClickObjectFunction: PropTypes.func.isRequired
}
```
`+` [Action](#action) `props`
- **data** - underlaying object with at least one non-empty property of the following: *geometry*, *polygonCoordinates* or *x* and *y*
- **type** - type of the object (FULLTEXT: 1, PARCEL: 2, ADDRESS: 3, SELECTION: 4, COORDINATES: 5, DRAWING: 6, GRID: 7, MODULE: 8)
- **onClick** - additional function to be called after an icon is clicked, to be performed right after the geometry is shown in map
- **localization** - object with localized strings
- **hidePanelsFunction** - function for hiding twigis panels
- **setFixedPanelsFunction** - function for manipulation with panels in PanelManager
- **setClickObjectFunction** - function for showing an object in a map

**Usage**:
``` javascript
render() {
    let localization = this.props.appInterface.tree.select('workspace', 'localization', 'actionsButtons').get();
    
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ActionSelect type={1}
                    data={this.item}
                    icon="point2"
                    localization={localization}
                    hidePanelsFunction={this.props.appInterface.actions.hidePanels}
                    setFixedPanelsFunction={this.props.appInterface.actions.setFixedPanels}
                    setClickObjectFunction={this.props.appInterface.actions.setClickObject}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### ActionSeparator
**Description**: Horizontal line for separating items. Mainly for usage within [ActionMenu](#actionmenu).

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/actionseparator1.png?raw=true)

**Props**:
```
ActionSeparator.propTypes = {
  className: PropTypes.string,
  condition: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.func
  ])
}
```
- **className** - additional class that appends the default "Action-separator" class
- **condition** - boolean or function value, that can indicate if the separator should be displayed at all

**Usage**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                    <div className="Panel-toolbarContent">
                        ...
                    </div>
                    <div className="Panel-toolbarActions">
                        <ActionMenu hybridMenu={false} 
                            anchor="right" 
                            translator={this.props.appInterface.tree.select('workspace', 'localization', 'root').get()}>
                            <Action title={"Edit"} 
                                condition={canUserEdit} 
                                onClick={this.handleEditItemClick.bind(this)} 
                                icon="edit" />
                            <Action title={"Delete"} 
                                condition={canUserDelete}
                                onClick={this.handleDeleteItemClick.bind(this)} 
                                icon="delete" />
                            <ActionSeparator/>
                            <Action title={"Add"} 
                                condition={canUserAddItem}
                                onClick={this.handleAddItemClick.bind(this)} 
                                icon="add" />
                        </ActionMenu>
                    </div>
                </div>
            <div className="Panel-content">
                ...
            </div>
        </div>
    )
}
```
[Back](#components)

#### AlertBox
**Description**: Twigis replacement for modal "alert" dialog.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/alertbox1.png?raw=true)

**Props**:
```
AlertBox.propTypes = {
  onClick: PropTypes.func.isRequired,
  text: PropTypes.string,
  btnText: PropTypes.string
}
```
- **onClick** - function to be called after the confirmation button is clicked
- **text** - message of the dialog
- **btnText** - text of the confirmation button

**Usage 1 - standalone**:
``` javascript
hideAlertBox() {
    this.setState({
        showAlertBox: false
    });
}

render() {
    return (
        <div>
            {this.state.showAlertBox && <AlertBox 
                 onClick={this.hideAlertBox.bind(this)}
                 text={"Item saved succesfully"}
                 btnText={"OK"}
            />}
        </div>
    )
}
```

**Usage 2 - twigis module**:
``` javascript
let callback = () => {
  // TODO  
};
this.props.appInterface.actions.showAlert("Item saved succesfully", callback, "OK");
```
or simply
``` javascript
this.props.appInterface.actions.showAlert("Item saved succesfully");
```
[Back](#components)

#### AppLink
**Description**: Theme-styled hyperlink. Can be configured to perform certain special twigis actions.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/applink1.png?raw=true)

**Props**:
```
AppLink.propTypes = {
  router: PropTypes.shape({
    push: PropTypes.func.isRequired
  }).isRequired,
  settings: PropTypes.shape({
    action: PropTypes.string.isRequired,
    parameters: PropTypes.object.isRequired
  }).isRequired,
  label: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number
  ]).isRequired,
  routerCopy: PropTypes.object,
  gridCopy: PropTypes.object,
  gridSettings: PropTypes.object,
  title: PropTypes.string,
  params: PropTypes.object,
  onClick: PropTypes.func,
  appLinkTypes: PropTypes.shape({
    EXTERNAL_URI: PropTypes.string.isRequired,
    SHOW_GRID: PropTypes.string.isRequired,
    SHOW_MODULE: PropTypes.string.isRequired,
    SHOW_DETAIL: PropTypes.string.isRequired
  }),
  getEntityNameFunction: PropTypes.func.isRequired,
  gridFetchSpecsForEntityFunction: PropTypes.func.isRequired,
  resolveActionFunction: PropTypes.func.isRequired,
  replaceParamsFunction: PropTypes.func.isRequired,
  data:PropTypes.object
}
```
- **router** - router object for managing browser navigation. Value passed by the DataGrid parent object
- **settings** - special object that can define behavior of the hyperlink (corresponds to the settings via twigis administration). It should have *action* and *parameters*
- **label** - hyperlink text
- **routerCopy** - for internal use only
- **gridCopy** - for internal use only
- **gridSettings** - for internal use only
- **title** - hyperlink tooltip
- **params** - object with additional information about an action (defined via *settings*). Should be in form of {panel: string}
- **onClick** - function to be called after the hyperlink was clicked and after an action (defined via *settings*) was performed
- **appLinkTypes**
- **getEntityNameFunction** - function for parsing an entity name from a dataendpoint
- **gridFetchSpecsForEntityFunction** - function for reading the specs of a certain entity
- **resolveActionFunction** - function for managing the default behavior of the action defined via twigis administration
- **replaceParamsFunction** - helper function for *resolveActionFunction*
- **data** - object upon which the action is to be performed

**Usage 1 - self handling**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <AppLink label={"Open contracts"} 
                    settings={{action: '', parameters: {}}} 
                    onClick={this.handleDisplayContractsClick.bind(this)} 
                    getEntityNameFunction={this.props.appInterface.api.getEntityName} 
                    gridFetchSpecsForEntityFunction={this.props.appInterface.actions.gridFetchSpecsForEntity} 
                    resolveActionFunction={this.props.appInterface.utils.resolveAction} 
                    replaceParamsFunction={this.props.appInterface.utils.replaceParams} 
                    appLinkTypes={this.props.appInterface.constants.AppLinkTypes} />
            </div>
        </div>
    )
}
```

**Usage 2 - open filtered grid**:
``` javascript
render() {
    let openMyOpenedTicketsSettings = {
        action: 'ShowGrid',
        parameters: {
            dataentity: 'DS/TICKETS',
            filter: `@FID_USER eq ${this.myUserId} and @STATE ne 'closed'`
        }
    };

    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <AppLink label={"Open my tickets"} 
                    settings={openMyOpenedTicketsSettings}
                    getEntityNameFunction={this.props.appInterface.api.getEntityName} 
                    gridFetchSpecsForEntityFunction={this.props.appInterface.actions.gridFetchSpecsForEntity} 
                    resolveActionFunction={this.props.appInterface.utils.resolveAction} 
                    replaceParamsFunction={this.props.appInterface.utils.replaceParams} 
                    appLinkTypes={this.props.appInterface.constants.AppLinkTypes} />
            </div>
        </div>
    )
}
```
[Back](#components)

#### BodyCell
**Description**: One cell in a [DataGrid](#datagrid).

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/bodycell1.png?raw=true)

**Props**:

```
BodyCell.propTypes = {
  type: PropTypes.string,
  data: PropTypes.array,
  rowIndex: PropTypes.number,
  columnKey: PropTypes.string,
  router: PropTypes.object,
  grid: PropTypes.object,
  localization: PropTypes.object,
  selectionSettings: PropTypes.object,
  onActionClick: PropTypes.func,
  isActionCellDisabled: PropTypes.bool,
  relation: PropTypes.object,
  formatDateTime: PropTypes.func.isRequired,
  getEntityNameFunction: PropTypes.func.isRequired,
  fetchEntityDescriptionFunction: PropTypes.func.isRequired,
  getAliasesFunction: PropTypes.func.isRequired,
  resolveActionFunction: PropTypes.func.isRequired,
  replaceParamsFunction: PropTypes.func.isRequired,
  gridFetchSpecsForEntityFunction: PropTypes.func.isRequired,
  appLinkTypes: PropTypes.shape({
    EXTERNAL_URI: PropTypes.string.isRequired,
    SHOW_GRID: PropTypes.string.isRequired,
    SHOW_MODULE: PropTypes.string.isRequired,
    SHOW_DETAIL: PropTypes.string.isRequired
  }),
  relationRoles: PropTypes.shape({
    CHILD: PropTypes.string.isRequired,
    PARENT: PropTypes.string.isRequired,
    MULTI: PropTypes.string.isRequired
  }),
  dataTypes: PropTypes.shape({
    BLOB: PropTypes.string.isRequired,
    CLOB: PropTypes.string.isRequired,
    DATE: PropTypes.string.isRequired,
    ENUM: PropTypes.string.isRequired,
    GEOM: PropTypes.string.isRequired,
    NUMBER: PropTypes.string.isRequired,
    UNKNOWN: PropTypes.string.isRequired,
    VARCHAR: PropTypes.string.isRequired,
    ACTION: PropTypes.string.isRequired,
  }),
  filterTypes: PropTypes.shape({
    EQUALS: PropTypes.string.isRequired,
    NOTEQUALS: PropTypes.string.isRequired,
    CONTAINS: PropTypes.string.isRequired,
    NOTCONTAINS: PropTypes.string.isRequired,
    GREATERTHAN: PropTypes.string.isRequired,
    GREATERTHANOREQUALS: PropTypes.string.isRequired,
    LESSERTHAN: PropTypes.string.isRequired,
    LESSERTHANOREQUALS: PropTypes.string.isRequired,
    BETWEEN: PropTypes.string.isRequired,
    ISNULL: PropTypes.string.isRequired,
    ISNOTNULL: PropTypes.string.isRequired,
    STARTSWITH: PropTypes.string.isRequired,
    ENDSWITH: PropTypes.string.isRequired,
    UNSPECIFIED: PropTypes.string.isRequired,
    IN: PropTypes.string.isRequired,
    NOTIN: PropTypes.string.isRequired
  }),
  columnName: PropTypes.string,
  customCellRendering: PropTypes.object,
  scale: PropTypes.number,
  precision: PropTypes.number,
  customRowToolsCellRenderer: PropTypes.func,
  customRowHighlighted: PropTypes.object,
  customRowClassName: PropTypes.string
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### CheckBox
**Description**: Theme-styled checkbox for twigis.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/checkbox1.png?raw=true)

**Props**:
```
CheckBox.propTypes = {
  name: PropTypes.string,
  suffix: PropTypes.any,
  active: PropTypes.bool,
  checked: PropTypes.bool,
  disabled: PropTypes.bool,
  value: PropTypes.any,
  onChange: PropTypes.func,
  title: PropTypes.string
}
```
- **name** - voluntary identifier of the input. Unique name is composed as '{name}-{suffix}'
- **suffix** - voluntary identifier of the input. Unique name is composed as '{name}-{suffix}'
- **active** - if false, checkbox is visibly grayed-out, however can be checked normally. Purely visual indication
- **checked** - state of the checkbox
- **disabled** - if true, checkebox is visible, but grayed-out and unable to be changed
- **value** - background representation of the checkbox value (important for multiple checkboxes at the same place)
- **onChange**
- **title** - tooltip of the checkbox

**Usage**:
``` javascript
toggleChecked(args) {
    this.setState({
        layerChecked: !this.state.layerChecked
    });
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <CheckBox name="layer-checkbox" 
                    suffix='online' 
                    checked={this.state.layerChecked} 
                    active={this.state.layerActive} 
                    onChange={this.toggleChecked.bind(this)} />
            </div>
        </div>
    )
}
```
[Back](#components)

#### ClearableInput
**Description**: Text field with a button for clearing the input text. Quite similar to [SearchField](#searchfield)

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/clearableinput1.png?raw=true)

**Props**:
```
ClearableInput.propTypes = {
  value: PropTypes.string,
  inputType: PropTypes.string,
  onChange: PropTypes.func.isRequired,
  placeholder: PropTypes.string
}
```
- **value** - the input text
- **inputType** - type of input. Can be 'text' or 'number'. Default value is 'text'
- **onChange**
- **placeholder** - placeholder (text for empty value) of the input

**Usage**:
``` javascript
handleCurrentSearchTextChanged(args) {
    if(!args || !args.length) {
        args = '';
    }
    this.setState({
        currentSearchText: args
    });
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ClearableInput value={this.state.currentSearchText}
                    onChange={this.handleCurrentSearchTextChanged.bind(this)}
                    placeholder={"Enter text to be searched"} 
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### CollapseCell
**Description**: One cell in a [DataGrid](#datagrid) designed for collapsing/expanding rows on mobile devices.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/collapsecell1.png?raw=true)

**Props**:
```
CollapseCell.propTypes = {
  data: PropTypes.array,
  selectionSettings: PropTypes.object,
  rowIndex: PropTypes.number,
  highlightedRow: PropTypes.number,
  expandedSettings: PropTypes.array,
  onClick: PropTypes.func
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### Color
**Description**: Component displaying a single color.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/color1.png?raw=true)

**Props**:
```
Color.propTypes = {
  color: PropTypes.string,
  onClick: PropTypes.func,
  selected: PropTypes.bool
}
```
- **color** - color (html/css format)
- **onClick**
- **selected** - graphical indication of whether color is selected

**Usage**:
``` javascript
handleColorClick(color) {
    this.setState({
        color: color
    });
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <Color
                    key={'red'}
                    color={'red'}
                    selected={this.state.color == 'red'}
                    onClick={this.handleColorClick.bind(this, 'red')}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### ColorSelect
**Description**: Component for choosing a single color of a few.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/colorselect1.png?raw=true)

**Props**:
```
ColorSelect.propTypes = {
  colors: PropTypes.arrayOf(PropTypes.string),
  selected: PropTypes.string,
  onChange: PropTypes.func
}
```
- **colors** - array of colors (html/css format)
- **selected** - selected color (html/css format)
- **onChange** - function to be called when color is selected

**Usage**:
``` javascript
selectColor(color) {
    this.setState({
        color: color
    });
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ColorSelect
                    selected={this.state.color}
                    colors={['#000000', '#fe0056', '#70ce3b', '#008cf9', '#ffc200', '#ae00b2']}
                    onChange={this.selectColor.bind(this)}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### ColumnFilter
**Description**: Component for defining filter criteria. Based on props can be transformed into [ColumnFilterDate](#columnfilterdate), [ColumnFilterEnum](#columnfilterenum), [ColumnFilterNumber](#columnfilternumber) or [ColumnFilterText](#columnfiltertext)

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/columnfilter1.png?raw=true)

**Props**:
```
ColumnFilter.propTypes = {
  column: PropTypes.object,
  filter: PropTypes.object,
  localization: PropTypes.object,
  onChange: PropTypes.func,
  onScrollToEndItemList: PropTypes.func,
  onChangeFilterValue: PropTypes.func,
  tableHeight: PropTypes.number,
  relationRoles: PropTypes.shape({
    CHILD: PropTypes.string.isRequired,
    PARENT: PropTypes.string.isRequired,
    MULTI: PropTypes.string.isRequired
  }),
  dataTypes: PropTypes.shape({
    BLOB: PropTypes.string.isRequired,
    CLOB: PropTypes.string.isRequired,
    DATE: PropTypes.string.isRequired,
    ENUM: PropTypes.string.isRequired,
    GEOM: PropTypes.string.isRequired,
    NUMBER: PropTypes.string.isRequired,
    UNKNOWN: PropTypes.string.isRequired,
    VARCHAR: PropTypes.string.isRequired,
    ACTION: PropTypes.string.isRequired,
    DATETIME: PropTypes.string.isRequired
  }),
  filterTypes: PropTypes.shape({
    EQUALS: PropTypes.string.isRequired,
    NOTEQUALS: PropTypes.string.isRequired,
    CONTAINS: PropTypes.string.isRequired,
    NOTCONTAINS: PropTypes.string.isRequired,
    GREATERTHAN: PropTypes.string.isRequired,
    GREATERTHANOREQUALS: PropTypes.string.isRequired,
    LESSERTHAN: PropTypes.string.isRequired,
    LESSERTHANOREQUALS: PropTypes.string.isRequired,
    BETWEEN: PropTypes.string.isRequired,
    ISNULL: PropTypes.string.isRequired,
    ISNOTNULL: PropTypes.string.isRequired,
    STARTSWITH: PropTypes.string.isRequired,
    ENDSWITH: PropTypes.string.isRequired,
    UNSPECIFIED: PropTypes.string.isRequired,
    IN: PropTypes.string.isRequired,
    NOTIN: PropTypes.string.isRequired
  }),
  enumTypes: PropTypes.shape({
    SIMPLE: PropTypes.string.isRequired,
    HIERARCHICAL: PropTypes.string.isRequired,
    PHYSICAL: PropTypes.string.isRequired,
    VIRTUAL: PropTypes.string.isRequired
  }),
  dateFormats: PropTypes.shape({
    DATE: PropTypes.string.isRequired,
    TIME: PropTypes.string.isRequired,
    DATE_TIME: PropTypes.string.isRequired,
    ISO: PropTypes.string.isRequired,
    ISO_DATE: PropTypes.string.isRequired,
    ISO_TIME: PropTypes.string.isRequired,
    VARIABLE_DATE: PropTypes.string.isRequired
  }),
  devices: PropTypes.object.isRequired,
  viewports: PropTypes.object.isRequired,
  focusSearchSelectFunction: PropTypes.func.isRequired,
  translator: PropTypes.object.isRequired,
  fieldValidator: PropTypes.func.isRequired,
  configLocalization: PropTypes.string.isRequired,
  isMobile: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.func
  ]),
  customColumnFilterFunction: PropTypes.func
}
```

**Usage**: `Since usage of ` [ColumnFilter](#columnfilter) ` is not trivial and requires twigis-specific props settings, we recommend using one of these directly: ` [ColumnFilterDate](#columnfilterdate), [ColumnFilterNumber](#columnfilternumber) or [ColumnFilterText](#columnfiltertext)

[Back](#components)

#### ColumnFilterDate
**Description**: Component for defining filter criteria of 'DateTime' values

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/columnfilterdate1.png?raw=true)

**Props**:
```
ColumnFilterDate.propTypes = {
  value: PropTypes.string,
  value2: PropTypes.string,
  type: PropTypes.string,
  onChange: PropTypes.func.isRequired,
  localization: PropTypes.object.isRequired,
  dateFormats: PropTypes.shape({
    DATE: PropTypes.string.isRequired,
    TIME: PropTypes.string.isRequired,
    DATE_TIME: PropTypes.string.isRequired,
    ISO: PropTypes.string.isRequired,
    ISO_DATE: PropTypes.string.isRequired,
    ISO_TIME: PropTypes.string.isRequired,
    VARIABLE_DATE: PropTypes.string.isRequired
  }),
  filterTypes: PropTypes.shape({
    EQUALS: PropTypes.string.isRequired,
    NOTEQUALS: PropTypes.string.isRequired,
    CONTAINS: PropTypes.string.isRequired,
    NOTCONTAINS: PropTypes.string.isRequired,
    GREATERTHAN: PropTypes.string.isRequired,
    GREATERTHANOREQUALS: PropTypes.string.isRequired,
    LESSERTHAN: PropTypes.string.isRequired,
    LESSERTHANOREQUALS: PropTypes.string.isRequired,
    BETWEEN: PropTypes.string.isRequired,
    ISNULL: PropTypes.string.isRequired,
    ISNOTNULL: PropTypes.string.isRequired,
    STARTSWITH: PropTypes.string.isRequired,
    ENDSWITH: PropTypes.string.isRequired,
    UNSPECIFIED: PropTypes.string.isRequired,
    IN: PropTypes.string.isRequired,
    NOTIN: PropTypes.string.isRequired
  }),
  devices: PropTypes.object.isRequired,
  viewports: PropTypes.object.isRequired,
  focusSearchSelectFunction: PropTypes.func.isRequired,
  configLocalization: PropTypes.any.isRequired,
  isMobile: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.func
  ])
}
```
- **value** - first DateTime argument for filtering (being used in most filter types)
- **value2** - second DateTime argument for filtering (being used only for 'between' filter type)
- **type** - current type of filtering - one of values from *filterTypes* prop - 'between', 'equals', 'notequals', 'greaterthan', 'greaterthanorequals', 'lesserthan', 'lesserthanorequals', 'isnull' or 'isnotnull'. Default value is 'between'
- **onChange** - function called after any change (type change, value change)
- **localization** - object with localized strings
- **dateFormats**
- **filterTypes**
- **devices** - collection of available device types ('PC', 'MOBIL', 'TABLET')
- **viewports** - object defining width thresholds between different device types
- **focusSearchSelectFunction** - twigis function for focusing combos
- **configLocalization** - locality identifier ('en', 'cz' ..). Default value is 'cz'
- **isMobile** - if true, a component is formatted for optimized mobile view

**Usage**:
``` javascript
state = {
    filterType: null,
    filterValue1: null,
    filterValue2: null
}

const objects = [
    {date: new Date(2024, 0, 1), name: 'January 1st 2024'},
    {date: new Date(2024, 1, 1), name: 'February 1st 2024'},
    {date: new Date(2024, 2, 1), name: 'March 1st 2024'},
    {date: new Date(2024, 3, 1), name: 'April 1st 2024'},
    {date: new Date(2024, 4, 1), name: 'May 1st 2024'},
    {date: new Date(2024, 5, 1), name: 'June 1st 2024'},
    {date: new Date(2024, 6, 1), name: 'July 1st 2024'},
    {date: new Date(2024, 7, 1), name: 'August 1st 2024'},
    {date: new Date(2024, 8, 1), name: 'September 1st 2024'},
    {date: new Date(2024, 9, 1), name: 'October 1st 2024'},
    {date: new Date(2024, 10, 1), name: 'November 1st 2024'},
    {date: new Date(2024, 11, 1), name: 'December 1st 2024'}
];

handleFilterChanged(filter) {
    this.setState({
        filterType: filter.type,
        filterValue1: (filter.param1 && filter.param1.length) ? filter.param1 : null,
        filterValue2: (filter.param2 && filter.param2.length) ? filter.param2 : null
    });
}

render() {
    let filteredObjects = objects.filter(x => {
        if(!this.state.filterType) {
            return true;
        }
        if(this.state.filterType == 'between') {
            if(this.state.filterValue1 && this.state.filterValue2) {
                let dateFrom = new Date(this.state.filterValue1);
                let dateTo = new Date(this.state.filterValue2);
                return x.date >= dateFrom && x.date <= dateTo;
            }
            else {
                if(this.state.filterValue1) {
                    let dateFrom = new Date(this.state.filterValue1);
                    return x.date >= dateFrom;
                }
                else if(this.state.filterValue2) {
                    let dateTo = new Date(this.state.filterValue2);
                    return x.date <= dateTo;
                }
            }
        }
        else if(this.state.filterType == 'equals') {
            // TODO
        }
        else if(this.state.filterType == 'notequals') {
            // TODO
        }
        else if(this.state.filterType == 'greaterthan') {
            // TODO
        }
        else if(this.state.filterType == 'greaterthanorequals') {
            // TODO
        }
        else if(this.state.filterType == 'lesserthan') {
            // TODO
        }
        else if(this.state.filterType == 'lesserthanorequals') {
            // TODO
        }
        else if(this.state.filterType == 'isnull') {
            // TODO
        }
        else if(this.state.filterType == 'isnotnull') {
            // TODO
        }
    });
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ColumnFilterDate type={this.state.filterType}
                    value={this.state.filterValue1}
                    value2={this.state.filterValue2}
                    onChange={this.handleFilterChanged.bind(this)}
                    localization={localization}
                    devices={this.props.appInterface.constants.MapControlTypes.Devices}
                    viewports={this.props.appInterface.constants.MapControlTypes.VIEWPORTS}
                    focusSearchSelectFunction={this.props.appInterface.actions.focusSearchSelect}
                    configLocalization={'en'}
                    isMobile={false}
                />
                <ul>
                    {filteredObjects.map((item, index) => {
                        return (
                            <li key={index} style={{margin: '8px'}}>{item.name}</li>
                        )
                    })}
                </ul>
            </div>
        </div>
    )
}
```
[Back](#components)

#### ColumnFilterEnum
**Description**: Component for defining filter criteria of 'Enum' values.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/columnfilterenum1.png?raw=true)

**Props**:
```
ColumnFilterEnum.propTypes = {
  values: PropTypes.object,
  items: PropTypes.array,
  onChange: PropTypes.func.isRequired,
  onScrollToEndItemList: PropTypes.func,
  onChangeFilterValue: PropTypes.func,
  column: PropTypes.object,
  tableHeight: PropTypes.number,
  itemsBeforeFilteringFromInput: PropTypes.array,
  localization: PropTypes.object.isRequired,
  translator: PropTypes.object.isRequired,
  fieldValidator: PropTypes.func.isRequired,
  enumTypes: PropTypes.shape({
    SIMPLE: PropTypes.string.isRequired,
    HIERARCHICAL: PropTypes.string.isRequired,
    PHYSICAL: PropTypes.string.isRequired,
    VIRTUAL: PropTypes.string.isRequired
  }),
  focusSearchSelectFunction: PropTypes.func,
  loadedItems: PropTypes.array
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### ColumnFilterNumber
**Description**: Component for defining filter criteria of 'Number' values

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/columnfilternumber1.png?raw=true)

**Props**:
```
ColumnFilterNumber.propTypes = {
  value: PropTypes.string,
  value2: PropTypes.string,
  type: PropTypes.string,
  onChange: PropTypes.func.isRequired,
  localization: PropTypes.object,
  filterTypes: PropTypes.shape({
    EQUALS: PropTypes.string.isRequired,
    NOTEQUALS: PropTypes.string.isRequired,
    CONTAINS: PropTypes.string.isRequired,
    NOTCONTAINS: PropTypes.string.isRequired,
    GREATERTHAN: PropTypes.string.isRequired,
    GREATERTHANOREQUALS: PropTypes.string.isRequired,
    LESSERTHAN: PropTypes.string.isRequired,
    LESSERTHANOREQUALS: PropTypes.string.isRequired,
    BETWEEN: PropTypes.string.isRequired,
    ISNULL: PropTypes.string.isRequired,
    ISNOTNULL: PropTypes.string.isRequired,
    STARTSWITH: PropTypes.string.isRequired,
    ENDSWITH: PropTypes.string.isRequired,
    UNSPECIFIED: PropTypes.string.isRequired,
    IN: PropTypes.string.isRequired,
    NOTIN: PropTypes.string.isRequired
  }),
  devices: PropTypes.object.isRequired,
  viewports: PropTypes.object.isRequired,
  focusSearchSelectFunction: PropTypes.func.isRequired
}
```
- **value** - first Number argument for filtering (being used in most filter types)
- **value2** - second Number argument for filtering (being used only for 'between' filter type)
- **type** - current type of filtering - one of values from *filterTypes* prop - 'between', 'equals', 'notequals', 'greaterthan', 'greaterthanorequals', 'lesserthan', 'lesserthanorequals', 'isnull', 'isnotnull', 'contains' or 'notcontains'. Default value is 'contains'
- **onChange** - function called after any change (type change, value change)
- **localization** - object with localized strings
- **filterTypes**
- **devices** - collection of available device types ('PC', 'MOBIL', 'TABLET')
- **viewports** - object defining width thresholds between different device types
- **focusSearchSelectFunction** - twigis function for focusing combos

``` javascript
state = {
    filterType: null,
    filterValue1: null,
    filterValue2: null
}

const objects = [
    {value: 1, name: '1'},
    {value: 2, name: '2'},
    {value: 3, name: '3'},
    {value: 4, name: '4'},
    {value: 5, name: '5'},
    {value: 6, name: '6'},
    {value: 7, name: '7'},
    {value: 8, name: '8'},
    {value: 9, name: '9'},
    {value: 10, name: '10'}
];

handleFilterChanged(filter) {
    this.setState({
        filterType: filter.type,
        filterValue1: (filter.param1 && filter.param1.length) ? filter.param1 : null,
        filterValue2: (filter.param2 && filter.param2.length) ? filter.param2 : null
    });
}

render() {
    let filteredObjects = objects.filter(x => {
        if(!this.state.filterType) {
            return true;
        }
        if(this.state.filterType == 'between') {
            if(this.state.filterValue1 && this.state.filterValue2) {
                let from = Number(this.state.filterValue1);
                let to = Number(this.state.filterValue2);
                return x.value >= from && x.value <= to;
            }
            else {
                if(this.state.filterValue1) {
                    let from = Number(this.state.filterValue1);
                    return x.value >= from;
                }
                else if(this.state.filterValue2) {
                    let to = Number(this.state.filterValue2);
                    return x.value <= to;
                }
            }
        }
        else if(this.state.filterType == 'equals') {
            // TODO
        }
        else if(this.state.filterType == 'notequals') {
            // TODO
        }
        else if(this.state.filterType == 'greaterthan') {
            // TODO
        }
        else if(this.state.filterType == 'greaterthanorequals') {
            // TODO
        }
        else if(this.state.filterType == 'lesserthan') {
            // TODO
        }
        else if(this.state.filterType == 'lesserthanorequals') {
            // TODO
        }
        else if(this.state.filterType == 'isnull') {
            // TODO
        }
        else if(this.state.filterType == 'isnotnull') {
            // TODO
        }
        else if(this.state.filterType == 'contains') {
            // TODO
        }
        else if(this.state.filterType == 'notcontains') {
            // TODO
        }
    });
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ColumnFilterNumber type={this.state.filterType}
                    value={this.state.filterValue1}
                    value2={this.state.filterValue2}
                    onChange={this.handleFilterChanged.bind(this)}
                    localization={localization}
                    devices={this.props.appInterface.constants.MapControlTypes.Devices}
                    viewports={this.props.appInterface.constants.MapControlTypes.VIEWPORTS}
                    focusSearchSelectFunction={this.props.appInterface.actions.focusSearchSelect}
                />
                <ul>
                    {filteredObjects.map((item, index) => {
                        return (
                            <li key={index} style={{margin: '8px'}}>{item.name}</li>
                        )
                    })}
                </ul>
            </div>
        </div>
    )
}
```
[Back](#components)

#### ColumnFilterText
**Description**: Component for defining filter criteria of 'String' values

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/columnfiltertext1.png?raw=true)

**Props**:
```
ColumnFilterText.propTypes = {
  value: PropTypes.string,
  type: PropTypes.string,
  onChange: PropTypes.func.isRequired,
  localization: PropTypes.object,
  filterTypes: PropTypes.shape({
    EQUALS: PropTypes.string.isRequired,
    NOTEQUALS: PropTypes.string.isRequired,
    CONTAINS: PropTypes.string.isRequired,
    NOTCONTAINS: PropTypes.string.isRequired,
    GREATERTHAN: PropTypes.string.isRequired,
    GREATERTHANOREQUALS: PropTypes.string.isRequired,
    LESSERTHAN: PropTypes.string.isRequired,
    LESSERTHANOREQUALS: PropTypes.string.isRequired,
    BETWEEN: PropTypes.string.isRequired,
    ISNULL: PropTypes.string.isRequired,
    ISNOTNULL: PropTypes.string.isRequired,
    STARTSWITH: PropTypes.string.isRequired,
    ENDSWITH: PropTypes.string.isRequired,
    UNSPECIFIED: PropTypes.string.isRequired,
    IN: PropTypes.string.isRequired,
    NOTIN: PropTypes.string.isRequired
  }),
  devices: PropTypes.object.isRequired,
  viewports: PropTypes.object.isRequired,
  focusSearchSelectFunction: PropTypes.func.isRequired
}
```
- **value** - String argument for filtering
- **type** - current type of filtering - one of values from *filterTypes* prop - 'equals', 'notequals', 'contains', 'notcontains', 'startswith', 'endswith', 'isnull' or 'isnotnull'. Default value is 'contains'
- **onChange** - function called after any change (type change, value change)
- **localization** - object with localized strings
- **filterTypes**
- **devices** - collection of available device types ('PC', 'MOBIL', 'TABLET')
- **viewports** - object defining width thresholds between different device types
- **focusSearchSelectFunction** - twigis function for focusing combos

**Usage**:
``` javascript
state = {
    filterType: null,
    filterValue1: ''
}

const objects = [
    {value: 1, name: 'Text1'},
    {value: 2, name: 'Text2'},
    {value: 3, name: 'Text3'},
    {value: 4, name: 'Text4'},
    {value: 5, name: 'Text5'},
    {value: 6, name: 'Txt6'},
    {value: 7, name: 'Texxt7'},
    {value: 8,  name: 'Txt8'},
    {value: 9, name: 'Teext9'},
    {value: 10, name: 'String10'}
];

handleFilterChanged(filter) {
    this.setState({
        filterType: filter.type,
        filterValue1: (filter.param1 && filter.param1.length) ? filter.param1 : ''
    });
}

render() {
    let filteredObjects = objects.filter(x => {
        if(!this.state.filterType) {
            return true;
        }
        if(this.state.filterType == 'contains') {
            if(this.state.filterValue1 && this.state.filterValue1.length) {
                return x.name.toLowerCase().indexOf(this.state.filterValue1.toLowerCase()) >= 0;
            }
            else {
                return true;
            }
        }
        else if(this.state.filterType == 'notcontains') {
            // TODO
        }
        else if(this.state.filterType == 'equals') {
            // TODO
        }
        else if(this.state.filterType == 'notequals') {
            // TODO
        }
        else if(this.state.filterType == 'startswith') {
            // TODO
        }
        else if(this.state.filterType == 'endswith') {
            // TODO
        }
        else if(this.state.filterType == 'isnull') {
            // TODO
        }
        else if(this.state.filterType == 'isnotnull') {
            // TODO
        }
    });
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ColumnFilterText type={this.state.filterType}
                    value={this.state.filterValue1}
                    onChange={this.handleFilterChanged.bind(this)}
                    localization={localization}
                    devices={this.props.appInterface.constants.MapControlTypes.Devices}
                    viewports={this.props.appInterface.constants.MapControlTypes.VIEWPORTS}
                    focusSearchSelectFunction={this.props.appInterface.actions.focusSearchSelect}
                />
                <ul>
                    {filteredObjects.map((item, index) => {
                        return (
                            <li key={index} style={{margin: '8px'}}>{item.name}</li>
                        )
                    })}
                </ul>
            </div>
        </div>
    )
}
```
[Back](#components)

#### ConfirmBox
**Description**: Twigis replacement for modal "confirm" dialog.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/confirmbox1.png?raw=true)

**Props**:
```
ConfirmBox.propTypes = {
  onClick: PropTypes.func.isRequired,
  text: PropTypes.string,
  confirmText: PropTypes.string,
  cancelText: PropTypes.string,
  isDelete: PropTypes.bool
}
```
- **onClick** - function to be called after the confirmation button is clicked
- **text** - message of the dialog
- **confirmText** - text of the confirmation button
- **cancelText** - text of the cancellation button
- **isDelete** - if true, the dialog is highlighted by red color

**Usage 1 - standalone**:
``` javascript
hideConfirmBox(callback, result) {
    if(result) {
        // TODO
    }
    this.setState({
        showConfirmBox: false
    });
}

render() {
    return (
        <div>
            {this.state.showConfirmBox && <ConfirmBox 
                 onClick={this.hideConfirmBox.bind(this)}
                 text={"Edit the item manually?"}
                 confirmText={"Yes"}
                 cancelText={"No"}
            />}
        </div>
    )
}
```

**Usage 2 - twigis module**:
``` javascript
let proceed = (result) => {
    if(result) {
        // TODO
    }
};
this.props.appInterface.actions.showConfirm("Edit the item manually?", proceed, "Yes", "No");
```
[Back](#components)

#### DataGrid
**Description**: Component for displaying data in a grid with immense configuration possibilities.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/datagrid1.png?raw=true)

**Props**:
```
DataGrid.propTypes = {
  width: PropTypes.number,
  height: PropTypes.number,
  data: PropTypes.arrayOf(PropTypes.object),
  columns: PropTypes.object,
  columnOrder: PropTypes.array,
  defaultColumnOrder: PropTypes.array,
  columnVisibility: PropTypes.object,
  defaultColumnVisibility: PropTypes.object,
  rowHeight: PropTypes.number,
  headerHeight: PropTypes.number,
  sortKey: PropTypes.string,
  defaultSortKey: PropTypes.string,
  sortDir: PropTypes.string,
  defaultSortDir: PropTypes.string,
  onSortChange: PropTypes.func,
  onColumnOrderChange: PropTypes.func,
  onToggleColumnVisibility: PropTypes.func,
  onJumpToEnd: PropTypes.func,
  onScrollBottom: PropTypes.func,
  onInit: PropTypes.func,
  onDestroy: PropTypes.func,
  grid: PropTypes.object,
  router: PropTypes.object,
  useColumnOrderFromState: PropTypes.bool,
  total: PropTypes.number,
  onFilterChange: PropTypes.func,
  rowsLocked: PropTypes.bool,
  onSelectionChanged: PropTypes.func,
  fetchAllDataFunc: PropTypes.func,
  fetchAllDataFuncParams: PropTypes.array,
  onScrollToEndItemList: PropTypes.func,
  onChangeFilterValue: PropTypes.func,
  relation: PropTypes.object,
  updateGridHandler: PropTypes.func,
  onCloseEditPanel: PropTypes.func,
  onOpenEditPanel: PropTypes.func,
  hideSelectAllCheckBox: PropTypes.bool,
  isActionCellDisabled: PropTypes.bool,
  sortTypes: PropTypes.shape({
    ASC: PropTypes.string.isRequired,
    DESC: PropTypes.string.isRequired,
    UNDEF: PropTypes.string,
  }),
  appLinkTypes: PropTypes.shape({
    EXTERNAL_URI: PropTypes.string.isRequired,
    SHOW_GRID: PropTypes.string.isRequired,
    SHOW_MODULE: PropTypes.string.isRequired,
    SHOW_DETAIL: PropTypes.string.isRequired
  }),
  relationRoles: PropTypes.shape({
    CHILD: PropTypes.string.isRequired,
    PARENT: PropTypes.string.isRequired,
    MULTI: PropTypes.string.isRequired
  }),
  dataTypes: PropTypes.shape({
    BLOB: PropTypes.string.isRequired,
    CLOB: PropTypes.string.isRequired,
    DATE: PropTypes.string.isRequired,
    ENUM: PropTypes.string.isRequired,
    GEOM: PropTypes.string.isRequired,
    NUMBER: PropTypes.string.isRequired,
    UNKNOWN: PropTypes.string.isRequired,
    VARCHAR: PropTypes.string.isRequired,
    ACTION: PropTypes.string.isRequired,
  }),
  filterTypes: PropTypes.shape({
    EQUALS: PropTypes.string.isRequired,
    NOTEQUALS: PropTypes.string.isRequired,
    CONTAINS: PropTypes.string.isRequired,
    NOTCONTAINS: PropTypes.string.isRequired,
    GREATERTHAN: PropTypes.string.isRequired,
    GREATERTHANOREQUALS: PropTypes.string.isRequired,
    LESSERTHAN: PropTypes.string.isRequired,
    LESSERTHANOREQUALS: PropTypes.string.isRequired,
    BETWEEN: PropTypes.string.isRequired,
    ISNULL: PropTypes.string.isRequired,
    ISNOTNULL: PropTypes.string.isRequired,
    STARTSWITH: PropTypes.string.isRequired,
    ENDSWITH: PropTypes.string.isRequired,
    UNSPECIFIED: PropTypes.string.isRequired,
    IN: PropTypes.string.isRequired,
    NOTIN: PropTypes.string.isRequired
  }),
  enumTypes: PropTypes.shape({
    SIMPLE: PropTypes.string.isRequired,
    HIERARCHICAL: PropTypes.string.isRequired,
    PHYSICAL: PropTypes.string.isRequired,
    VIRTUAL: PropTypes.string.isRequired
  }),
  dateFormats: PropTypes.shape({
    DATE: PropTypes.string.isRequired,
    TIME: PropTypes.string.isRequired,
    DATE_TIME: PropTypes.string.isRequired,
    ISO: PropTypes.string.isRequired,
    ISO_DATE: PropTypes.string.isRequired,
    ISO_TIME: PropTypes.string.isRequired,
    VARIABLE_DATE: PropTypes.string.isRequired
  }),
  localization: PropTypes.object.isRequired,
  formatDateTime: PropTypes.func.isRequired,
  getEntityNameFunction: PropTypes.func.isRequired,
  fetchEntityDescriptionFunction: PropTypes.func.isRequired,
  getAliasesFunction: PropTypes.func.isRequired,
  resolveActionFunction: PropTypes.func.isRequired,
  replaceParamsFunction: PropTypes.func.isRequired,
  gridFetchSpecsForEntityFunction: PropTypes.func.isRequired,
  selectionTypes: PropTypes.object.isRequired,
  closePanelFunction: PropTypes.func.isRequired,
  closeAllPanelsFunction: PropTypes.func.isRequired,
  hidePanelsFunction: PropTypes.func.isRequired,
  setFixedPanelsFunction: PropTypes.func.isRequired,
  setClickObjectFunction: PropTypes.func.isRequired,
  openPanelFunction: PropTypes.func.isRequired,
  devices: PropTypes.object.isRequired,
  viewports: PropTypes.object.isRequired,
  focusSearchSelectFunction: PropTypes.func.isRequired,
  fieldValidator: PropTypes.func.isRequired,
  triangleSrc: PropTypes.any.isRequired,
  setHoverObjectFunction: PropTypes.func.isRequired,
  isMobile: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.func
  ]),
  substituteFunction: PropTypes.func.isRequired,
  getSelectionFromRelationFunction: PropTypes.func.isRequired,
  configMaxFeatureLimit: PropTypes.number.isRequired,
  configLocalization: PropTypes.string.isRequired,
  customCellRendering: PropTypes.object,
  columnWidths: PropTypes.object,
  allowColumnResizing: PropTypes.bool,
  onColumnResized: PropTypes.func,
  columnsAsDetail: PropTypes.array,
  onRowClickAction: PropTypes.func,
  isRowActionEnabled: PropTypes.bool,
  isSelectorHidden: PropTypes.bool,
  isPhoneCompactView: PropTypes.bool,
  customRowHighlighted: PropTypes.object,
  customRowClassName: PropTypes.string,
  customCellClassNameFunc: PropTypes.func,
  customColumnFiltering: PropTypes.object
}
```

**Usage**:
`The DataGrid component is very complex and thus is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### DateCell
**Description**: One cell in a [DataGrid](#datagrid) suited for DateTime values.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/datecell1.png?raw=true)

**Props**:
```
DateCell.propTypes = {
  value: PropTypes.string,
  formatDateTime: PropTypes.func.isRequired
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### DateField
**Description**: Input field for the *date* datatype.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/datefield1.png?raw=true)

**Props**:
```
DateField.propTypes = {
  validate: PropTypes.func,
  localization: PropTypes.shape({
    wrongFormat: PropTypes.string.isRequired
  }).isRequired,
  onOpenOutsideWindow: PropTypes.func,
  name: PropTypes.string,
  disabled: PropTypes.bool,
  value: PropTypes.any,
  dateFormats: PropTypes.object,
  locale: PropTypes.string,
  dataType: PropTypes.string,
  isValidDate: PropTypes.func
}
```
`+` [Field](#field) `props`
- **validate** - function for managing validity of input (value)
- **localization** - object with localized strings
- **onOpenOutsideWindow** - function for handling scrolling if the popup would be opened outside of window view
- **name** - voluntary identifier of the field
- **disabled** - if true, input is visible, but grayed-out and unable to be changed
- **value** - selected date value
- **dateFormats** - object representing formatting of the various date types. Default value is  {DATE: 'DD.MM.YYYY', TIME: 'HH:mm:ss', DATE_TIME: 'DD.MM.YYYY HH:mm:ss', ISO: 'YYYY-MM-DDTHH:mm:ss.SSS', ISO_DATE: 'YYYY-MM-DD', ISO_TIME: 'HH:mm:ss.SSS', VARIABLE_DATE: 'D.M.YYYY'}
- **locale** - locality identifier ('en', 'cz' ..). Default value is 'cz'
- **dataType** - one of 'DateTime' or 'Date' values defining if time is relevant. Default value is 'DateTime'
- **isValidDate** - function that can determine, if any datetime values are valid or invalid. Has one datetime argument. Default value is null

**Usage**:
``` javascript
onComboOpenedOutsideOfWindow(event) {
    let scrollTop = 0;
    let closestPanelContent = this.props.appInterface.utils.findClosestAncestorById(this.form, 'Panel-content');
    let closestFieldToTarget = null;
    if (event.target) {
        closestFieldToTarget = this.props.appInterface.utils.findClosestAncestorByClassName(event.target, 'Field');
    }
    if (closestFieldToTarget) {
        scrollTop += closestFieldToTarget.offsetTop;
    }
    let closestGroupFieldToTarget = null;
    if (event.target) {
        closestGroupFieldToTarget = this.props.appInterface.utils.findClosestAncestorByClassName(event.target, 'GroupField');
    }
    if (closestGroupFieldToTarget) {
        scrollTop += closestGroupFieldToTarget.offsetTop;
    }
    if (closestPanelContent) {
        closestPanelContent.scrollTop = scrollTop ? scrollTop : closestPanelContent.scrollHeight;
    }
}

validateDateFrom(newValue) {
    if(!this.state.dateTo) {
        return;
    }
    if(!newValue || !newValue.length) {
        return;
    }
    // not including method for parsing date from string since it is too long and complex
    let from = Utils.parseDate(newValue, this.props.appInterface.applicationConfiguration.DateFormats.DATE_TIME);
    let to = new Date(this.state.dateTo);
    if(to <= from) {
        throw new Error("Date from must be lesser than date to");
    }
}

handleDateFromChanged(args) {
    if(!args || !args.length) {
        args = null;
    }
    this.setState({
        dateFrom: args
    });
}

render() {
    let localizationValidator = this.props.appInterface.tree.select('workspace', 'localization', 'validator').get();
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <DateField label={"Date from"}
                    localization={localizationValidator}
                    onOpenOutsideWindow={this.onComboOpenedOutsideOfWindow.bind(this)}
                    value={this.state.dateFrom} 
                    validate={this.validateDateFrom.bind(this)} 
                    empty={this.state.dateFrom === null || this.state.dateFrom === undefined} 
                    disabled={false} 
                    required={true} 
                    onChange={this.handleDateFromChanged.bind(this)}
                    dateFormats={this.props.appInterface.constants.DateFormats}
                    locale={this.props.appInterface.applicationConfiguration && 
                        this.props.appInterface.applicationConfiguration.user && 
                        this.props.appInterface.applicationConfiguration.user.localization || 
                        'cz'}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### DateInput
**Description**: DatePicker component for twiGIS.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/dateinput1.png?raw=true)

**Props**:
```
DateInput.propTypes = {
  date: PropTypes.bool,
  time: PropTypes.bool,
  value: PropTypes.string,
  required: PropTypes.bool,
  onChange: PropTypes.func,
  onError: PropTypes.func,
  placeholder: PropTypes.string,
  dateFormats: PropTypes.shape({
    DATE: PropTypes.string.isRequired,
    TIME: PropTypes.string.isRequired,
    DATE_TIME: PropTypes.string.isRequired,
    ISO: PropTypes.string.isRequired,
    ISO_DATE: PropTypes.string.isRequired,
    ISO_TIME: PropTypes.string.isRequired,
    VARIABLE_DATE: PropTypes.string.isRequired
  }),
  locale: PropTypes.string,
  isMobile: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.func
  ])
}
```
- **date** - if true, 'date' part of the datetime will be selectable
- **time** - if true, 'time' part of the datetime will be selectable
- **value** - current selected datetime value
- **required - if true, the value of the field is mandatory
- **onChange**
- **onError** - function to be called after a non-valid value is set
- **placeholder** - placeholder (text for empty value) of the input
- **dateFormats** - object defining format strings for various date formats. Default value is { DATE: 'DD.MM.YYYY', TIME: 'HH:mm:ss', DATE_TIME: 'DD.MM.YYYY HH:mm:ss', ISO: 'YYYY-MM-DDTHH:mm:ss', ISO_DATE: 'YYYY-MM-DD', ISO_TIME: 'HH:mm:ss', VARIABLE_DATE: 'D.M.YYYY' }
- **locale** - localization for the calendar. Default value is 'cz'
- **isMobile** - if true, a component is formatted for optimized mobile view

**Usage**:
``` javascript
handleDateChanged(value) {
    this.setState({
        date: value
    });
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <DateInput
                    date={true}
                    time={false}
                    value={this.state.date}
                    required={true}
                    onChange={this.handleDateChanged.bind(this)}
                    placeholder={"Enter the correct date"}
                    locale={'en'}
                    isMobile={false}
                    autofocus={false}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### Dropdown
**Description**: Dropdown menu, suitable if [ActionMenu](#actionmenu) is not sufficient enough.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/dropdown1.png?raw=true)

**Props**:
```
Dropdown.propTypes = {
  isOpen: PropTypes.bool,
  anchor: PropTypes.string,
  offset: PropTypes.number,
  isEnabled: PropTypes.bool,
  onToggle: PropTypes.func,
  children: PropTypes.any,
  icon: PropTypes.string,
  iconClassName: PropTypes.string,
  title: PropTypes.string,
  triangleSrc: PropTypes.any.isRequired
}
```
- **isOpen** - if true, the whole menu is visible, otherwise only the main icon is visible
- **anchor** - 'right' or 'left' alignment of the menu. Default value is 'left'
- **offset** - number of pixels the menu is shifted to left or right (dependant on *anchor* prop)
- **isEnabled** - if false, the menu does not appear upon clicking the main icon. Default value is true
- **onToggle** - function to be called after the menu appears or disappears
- **children** - collection of html elements to be displayed inside the menu
- **icon** - type of the main icon - for available types see [Icon](#icon). Default value is 'dots'
- **iconClassName** - additional class that appends the default "Icon" class
- **title** - tooltip of the main icon
- **triangleSrc** - path to the small triangle image

**Usage**:
``` javascript
handleResolutionItemClick(width, height) {
    // TODO
}

handleResetResolutionClick() {
    // TODO
}

handleRefreshResolutionClick() {
    // TODO
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <div id="dropdownDiv" style={{float: 'right'}}>
                    <Dropdown key="action-columns" 
                        title={"Settings"} 
                        anchor="right" 
                        offset={1} 
                        isEnabled={true} 
                        icon="settings" 
                        iconClassName="grid-Header-icon ActionMenu-icon" 
                        triangleSrc={require('./assets/images/triangle.png').default}>
                            <div id='dropdownHeader' style={{margin: "5px", padding: "3px"}}>
                                {"SETTINGS"}
                            </div>
                            <DropdownTab key="settings-tab" label={"Tab1"} icon="settings" name="settings">
                                <div style={{marginRight: '10px', marginLeft: '10px'}}>
                                    <Property label="Resolution" value={"1920 x 1080"}/>
                                </div>
                                <ActionSeparator condition={true}/>
                                <DropdownExtended label={"Resolutions"}
                                    labelIcon={"focus-field"}
                                    anchor={'right'}
                                    offset={1}
                                    onOpen={null}
                                >
                                    <div style={{padding: '10px', background: 'white', cursor: 'pointer'}} onClick={this.handleResolutionItemClick.bind(this, 1920, 1080)}>1920 x 1080</div>
                                    <div style={{padding: '10px', background: 'white', cursor: 'pointer'}} onClick={this.handleResolutionItemClick.bind(this, 1600, 1200)}>1600 x 1200</div>
                                    <div style={{padding: '10px', background: 'white', cursor: 'pointer'}} onClick={this.handleResolutionItemClick.bind(this, 1024, 768)}>1024 x 768</div>
                                </DropdownExtended>
                                <ActionSeparator condition={true}/>
                                <div style={{height: '30px', margin: '10px 15px 10px 15px', cursor: 'pointer', display: 'flex', alignItems: 'center'}}
                                    onClick={this.handleResetResolutionClick.bind(this)}>
                                    <Icon type="cross"/>
                                    {"Reset"}
                                </div>
                                <ActionSeparator condition={true}/>
                                <div style={{height: '30px', margin: '10px 15px 10px 15px', cursor: 'pointer', display: 'flex', alignItems: 'center'}}
                                    onClick={this.handleRefreshResolutionClick.bind(this)}>
                                    <Icon type="refresh"/>
                                    {"Refresh"}
                                </div>
                            </DropdownTab>
                        </Dropdown>
                    </div>
            </div>
        </div>
    )
}
```
[Back](#components)

#### DropdownExtended
**Description**: Child of the [Dropdown](#dropdown) that can be expanded for further content.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/dropdownextended1.png?raw=true)

**Props**:
```
DropdownExtended.propTypes = {
  children: PropTypes.any,
  label: PropTypes.string,
  labelIcon: PropTypes.string,
  anchor: PropTypes.string,
  offset: PropTypes.number,
  onOpen: PropTypes.func
}
```
- **children** - additional content to be displayed after item is expanded
- **label** - the main text of the item
- **labelIcon** - type of the icon, that can be displayed next to the text - for available types see [Icon](#icon). Default value is 'null
- **anchor** - 'right' or 'left' alignment of the menu. Default value is 'left'
- **offset** - number of pixels the menu is shifted to left or right (dependant on *anchor* prop)
- **onOpen** - function to be called after the side panel becomes expanded

**Usage**:
See the [Dropdown](#dropdown) example.

[Back](#components)

#### DropdownTab
**Description**: Voluntary inner component used by [Dropdown](#dropdown). Provides only minor adjustment related to overflow ability.

**Props**:
```
DropdownTab.propTypes = {
  children: PropTypes.any,
  fixed: PropTypes.bool
}
```
- **children** - collection of html elements to be displayed inside the dropdown
- **fixed** - if true, potential overflow is visible

[Back](#components)

#### EditRelationControl
**Description**: Control for displaying multiple items in edit mode. Main purpose is to edit relations 1:N or M:N. Includes capability for lazy loading displayed items.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/editrelationcontrol1.png?raw=true)

**Props**:
```
EditRelationControl.propTypes = {
  isSingleValue: PropTypes.bool,
  attach: PropTypes.func,
  required: PropTypes.bool,
  identifier: PropTypes.any,
  header: PropTypes.string,
  items: PropTypes.arrayOf(PropTypes.shape({
    caption: PropTypes.string.isRequired,
    id: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ]).isRequired,
    dataendpoint: PropTypes.string.isRequired,
    isAdded: PropTypes.bool,
    isRemoved: PropTypes.bool,
    geometry: PropTypes.object,
    changedProperties: PropTypes.object
  })),
  totalCount: PropTypes.number,
  currentCount: PropTypes.number,
  zIndex: PropTypes.number,
  isInitiallyOpen: PropTypes.bool,
  onRemoveItemClick: PropTypes.func,
  onLoadNextClick: PropTypes.func,
  onDetailClick: PropTypes.func,
  onAttachNewItemClick: PropTypes.func,
  onAttachFromGridClick: PropTypes.func,
  onAttachFromMapClick: PropTypes.func,
  onRemoveAllClick: PropTypes.func,
  hideAttachNewItemAction: PropTypes.bool,
  hideAttachFromGridAction: PropTypes.bool,
  hideAttachFromMapAction: PropTypes.bool,
  showRemoveAllAction: PropTypes.bool,
  defaultSelectorItems: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ]).isRequired,
    value: PropTypes.string.isRequired
  })),
  onDefaultSelectorScrollEnd: PropTypes.func,
  onDefaultSelectorFilterChange: PropTypes.func,
  onDefaultSelectorValuePicked: PropTypes.func,
  onOpenOutsideWindow: PropTypes.func,
  onUndoRemoveItemClick: PropTypes.func,
  validate: PropTypes.func,
  disabled: PropTypes.bool,
  highlightedItems: PropTypes.object,
  usedForFiles: PropTypes.bool,
  onFilesChosen: PropTypes.func,
  hideDefaultSelector: PropTypes.bool,
  localization: PropTypes.shape({
    validator: PropTypes.shape({
      wrongFormat: PropTypes.string.isRequired
    }).isRequired,
    root: PropTypes.object.isRequired,
    details: PropTypes.shape({
      add: PropTypes.string.isRequired,
      attachNewItem: PropTypes.string.isRequired,
      relation: PropTypes.shape({
        attachFromGrid: PropTypes.string.isRequired,
        attachFromMap: PropTypes.string.isRequired,
        attachNext: PropTypes.string.isRequired,
        removeRelation: PropTypes.string.isRequired,
        undoRemoveRelation: PropTypes.string.isRequired,
        noData: PropTypes.string.isRequired
      }).isRequired,
      loadNext: PropTypes.string.isRequired,
      loadNextMiddleWord: PropTypes.string.isRequired
    }).isRequired,
    search: PropTypes.shape({
      detailIconTitle: PropTypes.string.isRequired
    }).isRequired
  }).isRequired,
  onFocusSearchSelect: PropTypes.func,
  devicesConfig: PropTypes.shape({
    viewPorts: PropTypes.shape({
      MOBIL: PropTypes.shape({
        max: PropTypes.number.isRequired
      }).isRequired,
      TABLET: PropTypes.shape({
        min: PropTypes.number.isRequired, 
        max: PropTypes.number.isRequired
      }).isRequired,
      PC: PropTypes.shape({
        min: PropTypes.number.isRequired
      }).isRequired
    }).isRequired,
    devices: PropTypes.shape({
      MOBIL: PropTypes.string.isRequired,
      TABLET: PropTypes.string.isRequired,
      PC: PropTypes.string.isRequired
    }).isRequired
  }),
  setHoverObjectFunction: PropTypes.func,
  editableProperties: PropTypes.arrayOf(
    PropTypes.shape({
      propertyName: PropTypes.string.isRequired,
      propertyCaption: PropTypes.string.isRequired,
      isNumber: PropTypes.bool
    })
  ),
  onEditablePropertyChanged: PropTypes.func,
  disableAttachNewItemAction: PropTypes.bool,
  disableAttachFromGridAction: PropTypes.bool,
  disableAttachFromMapAction: PropTypes.bool,
  disableRemoveAllAction: PropTypes.bool,
  extraItemButtonIcon: PropTypes.string,
  extraItemButtonIconTitle: PropTypes.string,
  onExtraItemButtonClick: PropTypes.func,
  extraItemButtons: PropTypes.arrayOf(
    PropTypes.shape({
      icon: PropTypes.string.isRequired,
      iconTitle: PropTypes.string,
      click: PropTypes.func
    })
  ),
  extraMenuActions: PropTypes.arrayOf(
    PropTypes.shape({
      title: PropTypes.string.isRequired,
      icon: PropTypes.string.isRequired,
      condition: PropTypes.bool,
      disabled: PropTypes.bool,
      click: PropTypes.func
    })
  )
}
```
- **isSingleValue** - if true, only one item should be allowed as a child
- **attach** - function to be called during componentWillMount
- **required** - if true, at least one attached item is required
- **identifier** - unique identifier of the relation control. Useful when handling more relation controls' events with one common handler
- **header** - caption (header) of the control
- **items** - collection of items to be displayed. They need to have the *caption* and *dataendpoint* properties (dataendpoint should be a unique identifier). Voluntarily they can have *isAdded* or *isRemoved* properties which indicate what is to be done with the item. There can also be *changedProperties* which is used when *editableProperties* is set
- **totalCount** - number of all the items (including not yet loaded items)
- **currentCount** - number of loaded items
- **zIndex** - z order of the control. Useful mainly in the situation when there are two EditRelationControls above each other and their popups would interfere
- **isInitiallyOpen** - if false, the list of items is collapsed on the first render
- **onRemoveItemClick** - function to be called after the "remove" icon is clicked
- **onLoadNextClick** - function to be called after the link for loading next items is clicked
- **onDetailClick** - function to be called after the info icon on an item is clicked
- **onAttachNewItemClick** - function to be called after the action menu item for adding a new item is clicked
- **onAttachFromGridClick** - function to be called after the action menu item for adding an item from grid is clicked
- **onAttachFromMapClick** - function to be called after the action menu item for adding an item from map is clicked
- **onRemoveAllClick** - function to be called after the action menu item for removing all items is clicked
- **hideAttachNewItemAction** - if true, the action menu item for adding a new item is not displayed
- **hideAttachFromGridAction** - if true, the action menu item for adding an item from grid is not displayed
- **hideAttachFromMapAction** - if true, the action menu item for adding an item from map is not displayed
- **showRemoveAllAction** - if true, the action menu item for removing all items will be displayed
- **defaultSelectorItems** - list of items to be displayed in the default combo box with items which is visible unless *hideDefaultSelector* is set to true
- **onDefaultSelectorScrollEnd** - function to be called after a default selector combo box popup is scrolled down
- **onDefaultSelectorFilterChange** - function to be called after a searched text changes in a default selector combo box popup
- **onDefaultSelectorValuePicked** - function to be called after an item is chosen from a default selector combo box popup
- **onOpenOutsideWindow** - function for handling scrolling if the popup would be opened outside of window view
- **onUndoRemoveItemClick** - function to be called after a button for returning a removed item is clicked
- **validate** - function for managing validity of input (value)
- **disabled** - if true, control is visible, but grayed-out and unable to be changed
- **highlightedItems** - object representing highlighted items - each property with the same name as item's dataendpoint that is set to true represents that item to be highlighted
- **usedForFiles** - if true, the control becomes a multiple files chooser component
- **onFilesChosen** - function to be called after files has been chosen (only if *usedForFiles* is true)
- **hideDefaultSelector** - if true, the default selector combo box popup is hidden
- **localization** - object with localized strings
- **onFocusSearchSelect** - function called after an input of searched text is focused
- **devicesConfig**
- **setHoverObjectFunction** - function called after an item is hover over
- **editableProperties** - list of objects defining what properties of items become editable via gui - every item in the list should have a *propertyName* property and *isNumber* property
- **onEditablePropertyChanged** - function called after a property for an item has been changed
- **disableAttachNewItemAction** - if true, the action menu item for adding a new item is displayed, but grayed-out and unable to be clicked
- **disableAttachFromGridAction** - if true, the action menu item for attaching a new item from grid is displayed, but grayed-out and unable to be clicked
- **disableAttachFromMapAction** - if true, the action menu item for attaching a new item from map is displayed, but grayed-out and unable to be clicked
- **disableRemoveAllAction** - if true, the action menu item for removing all items is displayed, but grayed-out and unable to be clicked
- **extraItemButtonIcon** - if set (type of the icon - for available types see [Icon](#icon)), an extra icon appears for each item next to the default info icon
- **extraItemButtonIconTitle** - if *extraItemButtonIcon* is set, icon tooltip can be set via this property
- **onExtraItemButtonClick** - if *extraItemButtonIcon* is set, function to be called after the extra icon has been clicked
- **extraItemButtons** - array of extra buttons to appear next to the default info icon (for available types see [Icon](#icon))
- **extraMenuActions** - array of extra buttons to appear in the action menu (for available types see [Icon](#icon))

**Usage 1 - relation**:

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/editrelationcontrol3.png?raw=true)

``` javascript
var defaultDevicesConfig = null;

componentDidMount() {
    this.ensureDefaultDevicesConfig(this.props);
}

ensureDefaultDevicesConfig(props) {
    if(!defaultDevicesConfig && props.appInterface && props.appInterface.constants) {
        defaultDevicesConfig = {
            viewPorts: props.appInterface.constants.MapControlTypes.VIEWPORTS,
            devices: props.appInterface.constants.MapControlTypes.Devices
        };
    }
}

handleAttachFromGridClick() {
    alert("TODO");
}

handleAttachFromMapClick() {
    alert("TODO");
}

handleRemoveItemClick(item) {
    alert("TODO");
}

openDetail(item) {
    this.props.appInterface.actions.openPanel({
        type: 'detail',
        title: null,
        description: null,
        config: {
            type: 1,
            data: null,
            endpoint: item.dataendpoint
        }
    });
}

render() {
    let devices = [];
    for(let key in this.state.addedDevices) {
        let item = this.state.addedDevices[key];
        if(item) {
            devices.push({
                id: item.dataendpoint,
                caption: item.caption,
                dataendpoint: item.dataendpoint,
                description: item.description,
                isAdded: true,
                isRemoved: false
            });
        }
    }
    for(let key in this.state.removedDevices) {
        let item = this.state.removedDevices[key];
        if(item) {
            devices.push({
                id: item.dataendpoint,
                caption: item.caption,
                dataendpoint: item.dataendpoint,
                description: item.description,
                isRemoved: true
            });
        }
    }
    
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <EditRelationControl identifier="devices"
                    header={"Devices"}
                    items={devices}
                    defaultSelectorItems={[]}
                    totalCount={devices.length}
                    currentCount={devices.length}
                    required={true}
                    disabled={false}
                    zIndex={2}
                    isInitiallyOpen={true}
                    hideAttachNewItemAction={true}
                    hideAttachFromGridAction={false}
                    hideAttachFromMapAction={false}
                    hideDefaultSelector={true}
                    onAttachFromGridClick={this.handleAttachFromGridClick.bind(this)}
                    onAttachFromMapClick={this.handleAttachFromMapClick.bind(this)}
                    onRemoveItemClick={this.handleRemoveItemClick.bind(this)}
                    onDetailClick={this.openDetail.bind(this)}
                    localization={this.props.appInterface.tree.select('workspace', 'localization').get()}
                    devicesConfig={defaultDevicesConfig}
                />
            </div>
        </div>
    )
}
```

**Usage 2 - editable properties**:

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/editrelationcontrol2.png?raw=true)

``` javascript
var defaultDevicesConfig = null;

componentDidMount() {
    this.ensureDefaultDevicesConfig(this.props);
}

ensureDefaultDevicesConfig(props) {
    if(!defaultDevicesConfig && props.appInterface && props.appInterface.constants) {
        defaultDevicesConfig = {
            viewPorts: props.appInterface.constants.MapControlTypes.VIEWPORTS,
            devices: props.appInterface.constants.MapControlTypes.Devices
        };
    }
}

handleAddActivityClick() {
    alert("TODO");
}

handleRemoveActivityClick(item) {
    alert("TODO");
}

handleActivityPropertyChanged(item, field, value) {
    if(item.isAdded) {
        let newAddedActivities = JSON.parse(JSON.stringify(this.state.addedActivities));
        if(newAddedActivities[item.dataendpoint]) {
            newAddedActivities[item.dataendpoint][field.itemPropertyName] = field.type == 'number' ? Number(value) : value;
        }
        else {
            let obj = {};
            obj[field.itemPropertyName] = field.type == 'number' ? Number(value) : value;
            newAddedActivities[item.dataendpoint] = obj;
        }
        this.setState({
            addedActivities: newAddedActivities
        });
    }
    else {
        ...
    }
}

render() {
    let activities = [];
    for(let key in this.state.addedActivities) {
        let item = this.state.addedActivities[key];
        if(item) {
            activities.push({
                id: item.dataendpoint,
                caption: item.caption,
                dataendpoint: item.dataendpoint,
                description: item.description,
                dbName: item.dbName,
                dbDescription: item.dbDescription,
                dbOrder: item.dbOrder,
                isAdded: true,
                isRemoved: false
            });
        }
    }
    for(let key in this.state.removedActivities) {
        let item = this.state.removedActivities[key];
        if(item) {
            activities.push({
                id: item.dataendpoint,
                caption: item.caption,
                dataendpoint: item.dataendpoint,
                description: item.description,
                dbName: item.dbName,
                dbDescription: item.dbDescription,
                dbOrder: item.dbOrder,
                isRemoved: true
            });
        }
    }
    
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <EditRelationControl identifier="activities"
                    header={"Activities"}
                    items={activities}
                    defaultSelectorItems={[]}
                    totalCount={activities.length}
                    currentCount={activities.length}
                    required={false}
                    zIndex={2}
                    isInitiallyOpen={true}
                    hideAttachNewItemAction={false}
                    hideAttachFromGridAction={true}
                    hideAttachFromMapAction={true}
                    hideDefaultSelector={true}
                    onAttachNewItemClick={this.handleAddActivityClick.bind(this)}
                    onRemoveItemClick={this.handleRemoveActivityClick.bind(this)}
                    onDetailClick={null}
                    localization={this.props.appInterface.tree.select('workspace', 'localization').get()}
                    devicesConfig={defaultDevicesConfig}
                    editableProperties={
                        [{propertyName: 'dbName', propertyCaption: "Name", isNumber: false},
                        {propertyName: 'dbDescription', propertyCaption: "Description", isNumber: false},
                        {propertyName: 'dbOrder', propertyCaption: "Order", isNumber: true}]
                    }
                    onEditablePropertyChanged={this.handleActivityPropertyChanged.bind(this)}
                />
            </div>
        </div>
    )
}
```

**Usage 3 - files**:

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/editrelationcontrol4.png?raw=true)

``` javascript
var defaultDevicesConfig = null;

componentDidMount() {
    this.ensureDefaultDevicesConfig(this.props);
}

ensureDefaultDevicesConfig(props) {
    if(!defaultDevicesConfig && props.appInterface && props.appInterface.constants) {
        defaultDevicesConfig = {
            viewPorts: props.appInterface.constants.MapControlTypes.VIEWPORTS,
            devices: props.appInterface.constants.MapControlTypes.Devices
        };
    }
}

handleAttachNewFile(identifier, files) {
    if(identifier == 'files') {
        let newFiles = [];
        let fileNames = {};
        for(let file of this.state.attachments) {
            newFiles.push(file);
            fileNames[file.name] = true;
        }
        for(let file of files) {
            if(!fileNames[file.name]) {
                newFiles.push(file);
            }
        }
        this.setState({
            attachments: newFiles
        });
    }
}

handleRemoveFile(item, identifier) {
    ...
}

render() {
    let attachments = [];
    for(let file of this.state.attachments) {
        attachments.push({
            id: file.name,
            caption: file.name,
            dataendpoint: file.name,
            description: file.name,
            isAdded: true,
            isRemoved: false
        });
    }
    
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <EditRelationControl identifier="files"
                    header="Files"
                    usedForFiles={true}
                    items={attachments}
                    totalCount={attachments.length}
                    currentCount={attachments.length}
                    zIndex={1}
                    isInitiallyOpen={true}
                    onFilesChosen={this.handleAttachNewFile.bind(this)}
                    onRemoveItemClick={this.handleRemoveFile.bind(this)}
                    localization={this.props.appInterface.tree.select('workspace', 'localization').get()}
                    devicesConfig={defaultDevicesConfig}
                />
            </div>
        </div>
    )
}
```

[Back](#components)

#### ExtendedCheckBox
**Description**: Checkbox with text.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/extendedcheckbox1.png?raw=true)

**Props**:
```
ExtendedCheckBox.propTypes = {
  name: PropTypes.string,
  suffix: PropTypes.string,
  text: PropTypes.string,
  onChange: PropTypes.func,
  checked: PropTypes.bool,
  disabled: PropTypes.bool
}
```
- **name** - voluntary identifier of the input. Unique name is composed as '{name}-{suffix}'
- **suffix** - voluntary identifier of the input. Unique name is composed as '{name}-{suffix}'
- **text** - text next to the checkbox
- **onChange**
- **checked**
- **disabled** - if true, input is visible, but grayed-out and unable to be changed

**Usage**:
``` javascript
handleIncludeFoldersChanged(args) {
    if(args.currentTarget.checked) {
        this.setState({
            includeFolders: true
        });
    }
    else {
        this.setState({
            includeFolders: false
        });
    }
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ExtendedCheckBox text={"Include folders"} 
                    disabled={false} 
                    checked={this.state.includeFolders}
                    onChange={this.handleIncludeFoldersChanged.bind(this)}/>
            </div>
        </div>
    )
}
```
[Back](#components)

#### Field
**Description**: Abstract class for other *field* typed components. Not intended to be used by itself.

**Props**:
```
Field.propTypes = {
  attach: PropTypes.func,
  value: PropTypes.any,
  required: PropTypes.bool,
  empty: PropTypes.bool,
  onChange: PropTypes.func,
  name: PropTypes.string,
  geometry: PropTypes.object,
  validate: PropTypes.func,
  id: PropTypes.any,
  className: PropTypes.string,
  verticalCompactnessLevel: PropTypes.number,
  hideLabelOnSelected: PropTypes.bool,
  hideLabelOnFocus: PropTypes.bool,
  isBordered: PropTypes.bool,
  indicateChanges: PropTypes.bool,
  label: PropTypes.string,
  options: PropTypes.any,
  onFocus: PropTypes.func,
  onBlur: PropTypes.func,
  onGetEditObject: PropTypes.func,
  onActivateEditObject: PropTypes.func,
  onSetEditObjectRotation: PropTypes.func,
  validateOnChange: PropTypes.bool,
  validateOnBlur: PropTypes.bool,
  noScrollOnClick: PropTypes.bool,
  localization: PropTypes.shape({
    wrongFormat: PropTypes.string.isRequired
  }).isRequired
}
```
- **attach** - function to be called during componentWillMount
- **value** - value of the field (for example text in the TextField)
- **required** - if true, the value of the field is mandatory
- **empty** - if true, the field is considered empty (not filled in) despite value
- **onChange** - function to be called after value has changed
- **name** - voluntary identifier of the field
- **geometry** - for internal twigis use only
- **validate** - function for managing validity of input (value)
- **id** - html id of the default "Field" div element
- **className** - additional class that appends the default "Field" class
- **verticalCompactnessLevel** - number indicating *compactness* (margin) of the field - 0: margin 0, 1: margin bottom 20, 2: margin top 30, bottom 10. Default value is 2
- **hideLabelOnSelected** - if true, the header disappears when the value is not empty
- **hideLabelOnFocus** - if true, the header disappears when the field is focused
- **isBordered** - if true, the field has 1px border on all the sides
- **indicateChanges** - if true, the field graphically indicates change from default to another value
- **label** - caption (header) of the field
- **options** - additional class that appends the default "Field-label" class
- **onFocus**
- **onBlur**
- **onGetEditObject** - for internal twigis use only
- **onActivateEditObject** - for internal twigis use only
- **onSetEditObjectRotation** - for internal twigis use only
- **validateOnChange** - if the field should be validated on value change. Default value is true
- **validateOnBlur** - if the field should be validated on focus lost. Default value is true
- **noScrollOnClick** - if the automatic scrolling of the field into view should be turned off. Default value is false
- **localization** - object with localized strings

[Back](#components)

#### FileImage
**Description**: Image based on a file type or displaying a thumbnail.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/fileinput1.png?raw=true)

**Props**:
```
FileImage.propTypes = {
  name: PropTypes.string,
  type: PropTypes.oneOf([
    'image',
    'video',
    'document',
    'image360',
    'pdf'
  ]).isRequired,
  documentType: PropTypes.string,
  thumbnail: PropTypes.string,
  id: PropTypes.string,
  draggable: PropTypes.bool
}
```
- **name** - alt attribute for <img> element
- **type** - affects if thumbnails src will be used. Only 'image', 'image360' and 'pdf' support thumbnails
- **documentType** - file type associated with the image. One of these values: 'doc', 'dwg', 'pdf', 'ppt', 'txt', 'video', 'xls', 'zip' and 'unknown'. Will affect displayed icon unless thumbnail is set
- **thumbnail** - src to the displayed image if *type* is set to one of these values: 'image', 'image360' or 'pdf'
- **id** - identifier of the <img> element
- **draggable** - if true, the component be dragged via mouse or touch

**Usage**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <FileImage
                    id={"MediaCardImage1"}
                    name={'Presentation.pdf'}
                    type={'pdf'}
                    documentType={'pdf'}
                    thumbnail={null}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### FileInput
**Description**: Button for selecting files from the filesystem.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/fileinput1.png?raw=true)

**Props**:
```
FileInput.propTypes = {
  multiple: PropTypes.bool,
  onChange: PropTypes.func,
  disabled: PropTypes.bool,
  localization: PropTypes.shape({
    titleAddFile: PropTypes.string,
    textAddFile: PropTypes.string
  }),
  mobileOnlyCamera: PropTypes.bool
}
```
- **multiple** - if true, multiple files can be chosen simultaneously
- **onChange** - function called when the files are chosen
- **disabled** - if true, the control is disabled and cannot be clicked
- **localization** - object with localized strings
- **mobileOnlyCamera** - specifies which camera to use for capturing the file as image via mobile device. Can be 'user' or 'environment'

**Usage**:
``` javascript
handleFileChange(files) {
    // TODO
}

render() {
    let localization = this.props.appInterface.tree.select('workspace', 'localization', 'media').get();
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <FileInput onChange={this.handleFileChange.bind(this)} 
                    disabled={false} 
                    localization={localization} 
                    mobileOnlyCamera={'environment'} 
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### FulltextInput
**Description**: Fulltext search field with some improvements, like handling clearage and categories.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/fulltextinput1.png?raw=true)

**Props**:
```
FulltextInput.propTypes = {
  onChange: PropTypes.func,
  onFocus: PropTypes.func,
  onBlur: PropTypes.func,
  onKeyDown: PropTypes.func,
  onSwitch: PropTypes.func,
  onMouseDown: PropTypes.func,
  coordinates: PropTypes.any,
  onCloseCategories: PropTypes.func,
  onToggleCategory: PropTypes.func,
  categoryLabel: PropTypes.string,
  placeholder: PropTypes.string,
  toolTip: PropTypes.string,
  categoryImageSrc: PropTypes.any,
  categoryCancelImageSrc: PropTypes.any,
  isSwitchDisabled: PropTypes.bool,
  text: PropTypes.string,
  disabled: PropTypes.bool
}
```
- **onChange** - function to be called after input text has changed
- **onFocus** - function to be called after input received focus
- **onBlur** - function to be called after input lost focus
- **onKeyDown** - function to be called after a key has been pressed upon the input
- **onSwitch** - function to be called after a 'switch' icon was clicked. The switch icon is visible only if *coordinates* prop is set
- **onMouseDown** - function to be called after a mouse has been pressed upon the input
- **coordinates** - if not null, not undefined and not false, input is in the toggle-like mode with the switch icon
- **onCloseCategories** - function to be called after a 'search' icon was clicked
- **onToggleCategory** - function to be callsed after a 'category' icon was clicked
- **categoryLabel** - tooltip for the 'category' icon
- **placeholder** - placeholder (text for empty value) of the input
- **toolTip** - tooltip for the 'switch' icon
- **categoryImageSrc** - image source for the 'category' icon
- **categoryCancelImageSrc** - image source for the 'cancel category' icon
- **isSwitchDisabled** - if true, the 'switch' icon is visible, but grayed-out and unable to be clicked
- **text** - the text of the input
- **disabled** - if true, the input is disabled

**Usage**:
``` javascript
fulltextSearchInputChanged(text) {
    if(this.state.fulltextInput != text) {
        this.setState({
            fulltextInput: text
        });
        if(text.length < 2) {
            this.setState({
                fulltextResults: [],
                fulltextSelectedItem: null
            });
        }
        else {
            // TODO search matching items
        }
    }
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <div className="Property">
                        <h4 className="Property-label">{"Search"}</h4>
                        <FulltextInput text={this.state.fulltextInput} 
                            hideCategories={true} 
                            placeholder={"Type here to search"}
                            onChange={this.fulltextSearchInputChanged.bind(this)}
                        />
                    </div>
            </div>
        </div>
    )
}
```
[Back](#components)

#### FurtherDetailList
**Description**: Vertical list of items to be clicked for further details.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/furtherdetaillist1.png?raw=true)

**Props**:
```
FulltextInput.propTypes = {
  items: PropTypes.arrayOf(
    PropTypes.shape({
      text: PropTypes.string.isRequired,
      icon: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.element
      ]),
      identifier: PropTypes.any,
      isSelected: PropTypes.bool,
      smallText: PropTypes.string
    })
  ).isRequired,
  onItemClick: PropTypes.func
}
```
- **items** - list of items to be displayed
- **onItemClick**

**Usage**:
``` javascript
handleDetailFurtherDetailListClick(item) {
    this.setState({
        highlightedDetailTab: item.identifier
    });
    if(item.identifier == 'basicDetail') {
        // TODO
    }
    else if(item.identifier == 'buildings') {
        // TODO
    }
    else if(item.identifier == 'administrations') {
        // TODO
    }
}

render() {
    let detailTabs = [];
    detailTabs.push({
        text: "Detail",
        icon: 'list',
        identifier: 'basicDetail',
        isSelected: this.state.highlightedDetailTab == 'basicDetail'
    });
    detailTabs.push({
        text: "Buildings",
        icon: 'universal',
        identifier: 'buildings',
        isSelected: this.state.highlightedDetailTab == 'buildings'
    });
    detailTabs.push({
        text: "Administrations",
        icon: 'default',
        identifier: 'administrations',
        isSelected: this.state.highlightedDetailTab == 'administrations'
    });
    
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <FurtherDetailList items={detailTabs} 
                    onItemClick={this.handleDetailFurtherDetailListClick.bind(this)}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### GridFulltextSearch
**Description**: Input for fulltext searching using tags.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/gridfulltextsearch1.png?raw=true)

**Props**:
```
GridFulltextSearch.propTypes = {
  localization: PropTypes.object,
  columns: PropTypes.arrayOf(PropTypes.shape({
      name: PropTypes.string,
      caption: PropTypes.string
  })),
  tags: PropTypes.arrayOf(PropTypes.string),
  tagAdded: PropTypes.func,
  tagDeleted: PropTypes.func,
  checkedMapping: PropTypes.object.isRequired,
  onChangeCheckbox: PropTypes.func,
  onCheckAll: PropTypes.func,
  onUncheckAll: PropTypes.func,
  onChangeFilterValue: PropTypes.func,
  filterValue: PropTypes.string,
  triangleSrc: PropTypes.any.isRequired
}
```
- **localization** - object with localized strings
- **columns** - array of categories where search can be performed. Each category should be an object in the form of {name: string, caption: string}. These categories are then shown in a left-side popup as a list of check boxes
- **tags** - array of active tags to be searched
- **tagAdded** - function to be called when tag has been added
- **tagDeleted** - function to be called when tag has been deleted
- **checkedMapping** - object representing checked categories mentioned in the *columns* property. Object should be in the form of [key,value] where key is column.name and value is boolean for checked/unchecked
- **onChangeCheckbox** - function to be called when a category has been checked or unchecked
- **onCheckAll** - function to be called when all categories has been issued checked
- **onUncheckAll** - function to be called when all categories has been issued unchecked
- **onChangeFilterValue** - function to be called after categories search filter has been changed
- **filterValue** - current categories search filter
- **triangleSrc** - path to the small triangle image

**Usage**:
``` javascript
var FULLTEXT_SEARCH_ITEMS = [
    {
        caption: 'District',
        entity: 'WA/PO_WA_V_PIPE_DISTRICT_AGGR'
    },
    {
        caption: 'Water distribution',
        entity: 'WA/WA_V_CONDITION_DISTRIB'
    },
    {
        caption: 'Water source',
        entity: 'WA/WA_V_SOURCE_DISTRIB'
    }
];

state = {
    fulltextSearchCheckMapping: {
        'WA/PO_WA_V_PIPE_DISTRICT_AGGR': true,
        'WA/WA_V_CONDITION_DISTRIB': true,
        'WA/WA_V_SOURCE_DISTRIB': true
    },
    fulltextSearchTags: []
}

handleFulltextSearchCheckboxChanged(item, checked) {
    let newMapping = JSON.parse(JSON.stringify(this.state.fulltextSearchCheckMapping));
    newMapping[item.name] = checked;
    this.setState({
        fulltextSearchCheckMapping: newMapping
    });
}

handleFulltextSearchAllChecked() {
    let newMapping = {};
    for(let item of FULLTEXT_SEARCH_ITEMS) {
        newMapping[item.entity] = true;
    }
    this.setState({
        fulltextSearchCheckMapping: newMapping
    });
}

handleFulltextSearchAllUnchecked() {
    let newMapping = {};
    this.setState({
        fulltextSearchCheckMapping: newMapping
    });
}

handleFulltextSearchTagAdded(tag) {
    let newTags = [];
    if(this.state.fulltextSearchTags && this.state.fulltextSearchTags.length) {
        for(let t of this.state.fulltextSearchTags) {
            newTags.push(t);
        }
    }
    if(newTags.indexOf(tag) < 0) {
        newTags.push(tag);
        this.setState({
            fulltextSearchTags: newTags
        });
    }
}

handleFulltextSearchTagRemoved(tag) {
    let newTags = [];
    if(this.state.fulltextSearchTags && this.state.fulltextSearchTags.length) {
        for(let t of this.state.fulltextSearchTags) {
            if(t != tag) {
                newTags.push(t);
            }
        }
    }
    this.setState({
        fulltextSearchTags: newTags
    });
}

render() {
    let localizationForFulltextSearch = this.props.appInterface.tree.select('workspace', 'localization', 'grid').get();
    let searchColumns = FULLTEXT_SEARCH_ITEMS.map(item => {
            return {
                name: item.entity,
                caption: item.caption
            };
        });
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <div className="InlineField">
                    <GridFulltextSearch
                        localization={localizationForFulltextSearch}
                        columns={searchColumns}
                        checkedMapping={this.state.fulltextSearchCheckMapping}
                        onChangeCheckbox={this.handleFulltextSearchCheckboxChanged.bind(this)}
                        onCheckAll={this.handleFulltextSearchAllChecked.bind(this)}
                        onUncheckAll={this.handleFulltextSearchAllUnchecked.bind(this)}
                        tags={this.state.fulltextSearchTags}
                        tagAdded={this.handleFulltextSearchTagAdded.bind(this)}
                        tagDeleted={this.handleFulltextSearchTagRemoved.bind(this)}
                        filterValue={''}
                        triangleSrc={require('./assets/images/triangle.png').default}
                    />
                </div>
            </div>
        </div>
    )
}
```
[Back](#components)

#### HeaderActions
**Description**: Column header special action button for [DataGrid](#datagrid).

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/headeractions1.png?raw=true)

**Props**:
```
HeaderActions.propTypes = {
  column: PropTypes.object,
  filter: PropTypes.object,
  onFilterChange: PropTypes.func,
  columns: PropTypes.array,
  cellWidth: PropTypes.number,
  isEnabled: PropTypes.bool,
  localization: PropTypes.object,
  onToggle: PropTypes.func,
  onScrollToEndItemList: PropTypes.func,
  onChangeFilterValue: PropTypes.func,
  tableHeight: PropTypes.number,
  relationRoles: PropTypes.shape({
    CHILD: PropTypes.string.isRequired,
    PARENT: PropTypes.string.isRequired,
    MULTI: PropTypes.string.isRequired
  }),
  dataTypes: PropTypes.shape({
    BLOB: PropTypes.string.isRequired,
    CLOB: PropTypes.string.isRequired,
    DATE: PropTypes.string.isRequired,
    ENUM: PropTypes.string.isRequired,
    GEOM: PropTypes.string.isRequired,
    NUMBER: PropTypes.string.isRequired,
    UNKNOWN: PropTypes.string.isRequired,
    VARCHAR: PropTypes.string.isRequired,
    ACTION: PropTypes.string.isRequired,
  }),
  filterTypes: PropTypes.shape({
    EQUALS: PropTypes.string.isRequired,
    NOTEQUALS: PropTypes.string.isRequired,
    CONTAINS: PropTypes.string.isRequired,
    NOTCONTAINS: PropTypes.string.isRequired,
    GREATERTHAN: PropTypes.string.isRequired,
    GREATERTHANOREQUALS: PropTypes.string.isRequired,
    LESSERTHAN: PropTypes.string.isRequired,
    LESSERTHANOREQUALS: PropTypes.string.isRequired,
    BETWEEN: PropTypes.string.isRequired,
    ISNULL: PropTypes.string.isRequired,
    ISNOTNULL: PropTypes.string.isRequired,
    STARTSWITH: PropTypes.string.isRequired,
    ENDSWITH: PropTypes.string.isRequired,
    UNSPECIFIED: PropTypes.string.isRequired,
    IN: PropTypes.string.isRequired,
    NOTIN: PropTypes.string.isRequired
  }),
  enumTypes: PropTypes.shape({
    SIMPLE: PropTypes.string.isRequired,
    HIERARCHICAL: PropTypes.string.isRequired,
    PHYSICAL: PropTypes.string.isRequired,
    VIRTUAL: PropTypes.string.isRequired
  }),
  dateFormats: PropTypes.shape({
    DATE: PropTypes.string.isRequired,
    TIME: PropTypes.string.isRequired,
    DATE_TIME: PropTypes.string.isRequired,
    ISO: PropTypes.string.isRequired,
    ISO_DATE: PropTypes.string.isRequired,
    ISO_TIME: PropTypes.string.isRequired,
    VARIABLE_DATE: PropTypes.string.isRequired
  }),
  devices: PropTypes.object.isRequired,
  viewports: PropTypes.object.isRequired,
  focusSearchSelectFunction: PropTypes.func.isRequired,
  translator: PropTypes.object.isRequired,
  fieldValidator: PropTypes.func.isRequired,
  triangleSrc: PropTypes.any.isRequired,
  configLocalization: PropTypes.string.isRequired,
  isMobile: PropTypes.oneOfType([
    PropTypes.bool,
    PropTypes.func
  ]),
  customColumnFiltering: PropTypes.object
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### HeaderCell
**Description**: Special cell in a [DataGrid](#datagrid) suited for grid header.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/headercell1.png?raw=true)

**Props**:
```
HeaderCell.propTypes = {
  onSortChange: PropTypes.func,
  columnKey: PropTypes.string,
  sortDir: PropTypes.any,
  label: PropTypes.string,
  actions: PropTypes.object,
  isFiltered: PropTypes.bool,
  children: PropTypes.any,
  maxWidth: PropTypes.number,
  width: PropTypes.any,
  sortTypes: PropTypes.shape({
    ASC: PropTypes.string.isRequired,
    DESC: PropTypes.string.isRequired,
    UNDEF: PropTypes.string,
  })
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### HierarchicalSelectField
**Description**: Specialized component for multiple combos, where they depend on each other in a hierarchy-like way.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/hierarchicalselectfield1.png?raw=true)

**Props**:
```
HierarchicalSelectField.propTypes = {
  attach: PropTypes.func,
  required: PropTypes.bool,
  header: PropTypes.string,
  items: PropTypes.arrayOf(
    PropTypes.shape({
      data: PropTypes.any,
      awaitingData: PropTypes.bool,
      value: PropTypes.any,
      label: PropTypes.string,
      domainDictionary: PropTypes.object,
      disabled: PropTypes.bool
    })
  ),
  nullable: PropTypes.bool,
  onSelectedItemChanged: PropTypes.func,
  onComboScrollEnd: PropTypes.func,
  onComboFilterChange: PropTypes.func,
  onComboToggle: PropTypes.func,
  onClearAll: PropTypes.func,
  onOpenOutsideWindow: PropTypes.func,
  validate: PropTypes.func,
  indicateChanges: PropTypes.bool,
  renderMainValue: PropTypes.bool,
  valueChangedFromPropsCausesChange: PropTypes.bool,
  localization: PropTypes.shape({
    validator: PropTypes.shape({
      wrongFormat: PropTypes.string.isRequired
    }).isRequired,
    editing: PropTypes.shape({
      clearDomainDictionariesButton: PropTypes.string.isRequired
    }).isRequired
  }).isRequired,
  devicesConfig: PropTypes.shape({
		viewPorts: PropTypes.shape({
			MOBIL: PropTypes.shape({
				max: PropTypes.number.isRequired
			}).isRequired,
			TABLET: PropTypes.shape({
				min: PropTypes.number.isRequired, 
				max: PropTypes.number.isRequired
			}).isRequired,
			PC: PropTypes.shape({
				min: PropTypes.number.isRequired
			}).isRequired
		}).isRequired,
		devices: PropTypes.shape({
			MOBIL: PropTypes.string.isRequired,
			TABLET: PropTypes.string.isRequired,
			PC: PropTypes.string.isRequired
		}).isRequired
	}),
  onFocusSearchSelect: PropTypes.func
}
```
- **attach** - function to be called during componentWillMount
- **required** - if true, the value of the field is mandatory
- **header** - caption of the whole hierarchy field
- **items** - collection of items representing each level of hierarchy. Each item should have at least these properties: *label* (caption), *domainDictionary* (identifier of a level), *data* (list of choices {id: number, value: string}), *awaitingData* (if data are still being obtained), *value* (id of selected item), *disabled* (if current level should be disabled)
- **nullable** - if true, all the combo levels also contain an empty record
- **onSelectedItemChanged** - function to be called after an item in any level was selected
- **onComboScrollEnd** - function to be called after list of items in any combo is scrolled to the bottom
- **onComboFilterChange** - function to be called after searched text in any combo has changed
- **onComboToggle** - function to be called after any combo has been toggled
- **onClearAll** - function to be called after a button for clearing all the levels was clicked
- **onOpenOutsideWindow** - function for handling scrolling if the combo box and corresponding popup items would be opened outside of window view
- **validate** - function for validation of the last level combo box value
- **indicateChanges** - if true, the field graphically indicates change from default to another value
- **renderMainValue** - if false, when a last item is selected, the *onSelectedItemChanged* event is fired but the item is not internally set as selected. Default value is true
- **valueChangedFromPropsCausesChange** - if true and value of any level combo box is changed via props, the change is still indicated and propagated. Default value is false
- **localization** - object with localized strings
- **devicesConfig** - function called after an input of searched text is focused

**Usage**:
``` javascript
const EUROPE = {id: 1, value: 'Europe'};
const ASIA = {id: 2, value: 'Asia'};
const GERMANY = {id: 3, value: 'Germany'};
const FRANCE = {id: 4, value: 'France'};
const BRITAIN = {id: 5, value: 'Britain'};
const INDIA = {id: 6, value: 'India'};
const BERLIN = {id: 7, value: 'Berlin'};
const MUNICH = {id: 8, value: 'Munich'};
const PARIS = {id: 9, value: 'Paris'};
const LONDON = {id: 10, value: 'London'};
const MANCHESTER = {id: 11, value: 'Manchester'};
const DELHI = {id: 12, value: 'Delhi'};
const MUMBAI = {id: 13, value: 'Mumbai'};

const ALL_CONTINENTS = [EUROPE, ASIA];
const CONTINENTS_STATES_MAPPING = {
    1: [GERMANY, FRANCE, BRITAIN],
    2: [INDIA]
};
const STATES_CITIES_MAPPING = {
    3: [BERLIN, MUNICH],
    4: [PARIS],
    5: [LONDON, MANCHESTER],
    6: [DELHI, MUMBAI]
};


state = {
    selectedContinent: null,
    selectedState: null,
    selectedCity: null,
    continentFilter: '',
    stateFilter: '',
    cityFilter: ''
}

validateCity() {
    if(!this.state.selectedCity) {
        let msg = this.props.appInterface.tree.select('workspace', 'localization', 'validator', 'isNull').get();
        throw new Error(msg);
    }
}

handleClearAllHierarchyClick() {
    this.setState({
        selectedContinent: null,
        selectedState: null,
        selectedCity: null,
        continentFilter: '',
        stateFilter: '',
        cityFilter: ''
    });
}

handleHierarchySelectFieldFilterChange(propertyName, filterValue) {
    if(propertyName == 'continent') {
        this.setState({
            continentFilter: filterValue
        });
    }
    else if(propertyName == 'state') {
        this.setState({
            stateFilter: filterValue
        });
    }
    else if(propertyName == 'city') {
        this.setState({
            cityFilter: filterValue
        });
    }
}

handleHierarchyItemChange(hierarchy, item) {
    if(hierarchy) {
        if(hierarchy.propertyName == 'continent') {
            this.setState({
                selectedContinent: item
            });
        }
        else if(hierarchy.propertyName == 'state') {
            this.setState({
                selectedState: item
            });
        }
        else if(hierarchy.propertyName == 'city') {
            this.setState({
                selectedCity: item
            });
        }
    }
}

render() {
    let localization = this.props.appInterface.tree.select('workspace', 'localization').get();
    let localizationSearchSelect = localization.searchSelect;
    let localizationValidator = localization.validator;
    
    let hierarchyItems = [];
    hierarchyItems.push({
        label: "Continent",
        domainDictionary: "continent",
        propertyName: "continent",
        data: (this.state.continentFilter && this.state.continentFilter.length) ? ALL_CONTINENTS.filter(x => x.value.toLowerCase().indexOf(this.state.continentFilter.toLowerCase()) >= 0) : ALL_CONTINENTS,
        value: this.state.selectedContinent ? this.state.selectedContinent.id : null,
        awaitingData: false,
        disabled: false
    });
    hierarchyItems.push({
        label: "State",
        domainDictionary: "state",
        propertyName: "state",
        data: this.state.selectedContinent ? ((this.state.stateFilter && this.state.stateFilter.length) ? CONTINENTS_STATES_MAPPING[this.state.selectedContinent.id].filter(x => x.value.toLowerCase().indexOf(this.state.stateFilter.toLowerCase()) >= 0) : CONTINENTS_STATES_MAPPING[this.state.selectedContinent.id]) : [],
        value: this.state.selectedState ? this.state.selectedState.id : null,
        awaitingData: false,
        disabled: Boolean(!this.state.selectedContinent)
    });
    hierarchyItems.push({
        label: "City",
        domainDictionary: "city",
        propertyName: "city",
        data: this.state.selectedState ? ((this.state.cityFilter && this.state.cityFilter.length) ? STATES_CITIES_MAPPING[this.state.selectedState.id].filter(x => x.value.toLowerCase().indexOf(this.state.cityFilter.toLowerCase()) >= 0) : STATES_CITIES_MAPPING[this.state.selectedState.id]) : [],
        value: this.state.selectedCity ? this.state.selectedCity.id : null,
        awaitingData: false,
        disabled: Boolean(!this.state.selectedState)
    });

    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <HierarchicalSelectField header={"Target city"}
                    nullable={false}
                    items={hierarchyItems}
                    onSelectedItemChanged={this.handleHierarchyItemChange.bind(this)}
                    onComboScrollEnd={null}
                    onComboFilterChange={this.handleHierarchySelectFieldFilterChange.bind(this)}
                    onComboToggle={null}
                    onClearAll={this.handleClearAllHierarchyClick.bind(this)}
                    validate={this.validateCity.bind(this)}
                    required={true}
                    localization={{...localization, ...localizationValidator, ...localizationSearchSelect}}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### Icon
**Description**: Icon colored by the current application theme. It is based on the custom font, so it is scaled not with *widht* and *height*, but with *fontsize*.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/icon1.png?raw=true)

Icons are defined by their **type**. The list of available types are listed here (exclude the ".svg"):

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/icon2.png?raw=true)

**Props**:
```
Icon.propTypes = {
  type: PropTypes.string.isRequired,
  className: PropTypes.string,
  toolTip: PropTypes.string,
  style: PropTypes.object,
  onClick: PropTypes.func
}
```
- **type** - as stated above, a unique identifier of an image
- **className** - additional class that appends the default "Icon" class
- **toolTip** - tooltip displayed as a hover effect
- **style** - additional style object to be applied to the "Icon" element
- **onClick**

**Usage**:
``` javascript
render() {
    return (
        <div className="Property">
            <h4 className="Property-label">{"Find in map"}</h4>
            <div className={className('Tool Tool--point', {'is-active': this.state.highlightingAddedDeviceFromMap})} onClick={this.toggleHighlightingDeviceFromMap.bind(this)}>
                <Icon type="point"/>
            </div>
        </div>
    )
}
```
[Back](#components)

#### Image
**Description**: Image with a little extensive capabilities, like indication of loading the image.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/image1.png?raw=true)

**Props**:
```
Image.propTypes = {
  color: PropTypes.string,
  src: PropTypes.string,
  id: PropTypes.string,
  alt: PropTypes.string,
  className: PropTypes.string
}
```
- **color** - color of the loading spinner
- **src** - source url to the image
- **id** - id attribute of the <img> element
- **alt** - alt attribute of the <img> element
- **className** - class attribute of the <img> element

**Usage**:
``` javascript
render() {
    return (
        <div className="Property">
            <Image
                color={"gray"}
                id={"MediaCardImage1"}
                src={'img/LogCabin.png'}
                className={"galery-image"}
            />
        </div>
    )
}
```
[Back](#components)

#### MultilineTextField
**Description**: Text input that allows multiple lines.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/multilinetextfield1.png?raw=true)

**Props**:
```
MultilineTextField.propTypes = {
  name: PropTypes.string,
  maxLength: PropTypes.number,
  disabled: PropTypes.bool,
  readOnly: PropTypes.bool
}
```
`+` [Field](#field) `props`
- **name** - name attribute of the html textarea
- **maxLength** - maximum number of characters allowed in the input
- **disabled** - if true, input is visible, but grayed-out and unable to be changed
- **readOnly** - if true, input is visible, but unable to be changed

**Usage**:
``` javascript
handleDescriptionChanged(args) {
    this.setState({
        description: args
    });
}

render() {
    let localizationValidator = this.props.appInterface.tree.select('workspace', 'localization', 'validator').get();
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <MultilineTextField label={"Description"} 
                    disabled={false} 
                    value={this.state.description} 
                    verticalCompactnessLevel={2} 
                    isBordered={false} 
                    localization={localizationValidator} 
                    required={true} 
                    onChange={this.handleDescriptionChanged.bind(this)}/>
            </div>
        </div>
    )
}
```
[Back](#components)

#### MultilineTextFieldCountIndicator
**Description**: Indicator for number of characters in a certain text field. Can be used in other manner, but main purpose is paired with [MultilineTextField](#multilinetextfield).

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/multilinetextfieldcountindicator1.png?raw=true)

**Props**:
```
MultilineTextFieldCountIndicator.propTypes = {
  value: PropTypes.number,
  maxValue: PropTypes.number.isRequired,
  prefix: PropTypes.string,
  suffix: PropTypes.string,
  className: PropTypes.string
}
```
- **value** - current value (current count)
- **maxValue** - maximum value (count) allowed
- **prefix** - text to be displayed before the count indicator
- **suffix** - text to be displayed after the count indicator
- **className** - additional class that appends the default "MultilineTextFieldCountIndicator" class

**Usage**:
``` javascript
handleSmsTextChanged(args) {
    this.setState({
        smsText: (args && args.length) ? args : null
    });
}

render() {
    let localization = this.props.appInterface.tree.select('workspace', 'localization').get();
    let localizationValidator = localization.validator;
    
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <MultilineTextField label={"Sms text"} 
                    disabled={false} 
                    value={this.state.smsText} 
                    verticalCompactnessLevel={2} 
                    isBordered={false} 
                    localization={localizationValidator} 
                    required={true} 
                    maxLength={160}
                    onChange={this.handleSmsTextChanged.bind(this)}
                />
                <MultilineTextFieldCountIndicator value={(this.state.smsText && this.state.smsText.length) ? this.state.smsText.length : 0} 
                    className={(this.state.smsText && this.state.smsText.length > 160) ? "overflow" : null}
                    maxValue={160}
                    prefix={"Number of characters:"}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### NavIcon
**Description**: Icon specifically designed for left module menu.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/navicon1.png?raw=true)

**Props**:
```
NavIcon.propTypes = {
  content: PropTypes.any,
  className: PropTypes.string
}
```
- **content** - either <svg> string content or base64 encoded image
- **className** - additional class that appends the default "Nav-customIcon" class

**Usage**:
``` javascript
const moduleIcon = `
<svg viewBox="0 0 30 30">
    <path d="M5.25 6.75v18h19.5v-7.661L22.5 19.5v3h-15V9h5.25L15 6.75H5.25z"/>
    <path d="M27.75 10.854l-7.5 7.896V13.5h-1.5c-3.996 0-5.848 1.266-8.25 4.5-.02-7.054 6.144-9.753 8.25-9.75 1.547.002.023 0 1.5 0V3l7.5 7.854z"/>
</svg>
`

render() {
    return (
        <div>
            <NavIcon content={moduleIcon} className="default-module-icon" />
        </div>
    )
}
```
[Back](#components)

#### NumberCell
**Description**: One cell in a [DataGrid](#datagrid) suited for Number values.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/numbercell1.png?raw=true)

**Props**:
```
NumberCell.propTypes = {
  value: PropTypes.any,
  scale: PropTypes.number,
  precision: PropTypes.number
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### NumberField
**Description**: Classic input field of type number. Input characters are ignored.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/numberfield1.png?raw=true)

**Props**:
```
NumberField.propTypes = {
  name: PropTypes.string,
  step: PropTypes.any,
  disabled: PropTypes.bool,
  readOnly: PropTypes.bool,
  title: PropTypes.string
}
```
`+` [Field](#field) `props`
- **name** - voluntary identifier of the field
- **step** - step by which the value is incremented or decremented by using the up or down arrows
- **disabled** - if true, input is visible, but grayed-out and unable to be changed
- **readOnly** - if true, input is visible, but unable to be changed
- **title** - tooltip of the input

**Usage**:
``` javascript
valueInRangeValidator(min, max, value, props) {
    if(!props.required &&  (value === undefined || value === null || value === '')) {
        return;
    }

    if(typeof value != 'number') {
        throw new Error("Value must be number");
    }

    if(min !== undefined && min !== null) {
        if(max !== undefined && max !== null) {
            if(value < min || value > max) {
                throw new Error(`Value must be between ${min} and ${max}`);
            }
        }
        else {
            if(value < min) {
                throw new Error(`Value must be greater than or equal to ${min}`);
            }
        }
    }
    else {
        if(max !== undefined && max !== null) {
            if(value > max) {
                throw new Error(`Value must be lesser than or equal to ${max}`);
            }
        }
    }
}

handleProgressChanged(args) {
    this.setState({
        progress: args
    });
}

render() {
    let localizationValidator = this.props.appInterface.tree.select('workspace', 'localization', 'validator').get();
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <NumberField label={"Progress"}
                    disabled={false}
                    value={this.state.progress}
                    validate={this.valueInRangeValidator.bind(this, 0, 100)}
                    verticalCompactnessLevel={2}
                    isBordered={false}
                    localization={localizationValidator} 
                    required={false} 
                    step={0.1}
                    title={"Progress"}
                    onChange={this.handleProgressChanged.bind(this)}/>
            </div>
        </div>
    )
}
```
[Back](#components)

#### ObjectCard
**Description**: Representation of any item, mostly useful for usage in a list of items. Hover and icons are the main features.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/objectcard1.png?raw=true)

**Props**:
```
ObjectCard.propTypes = {
  onSetHoverObject: PropTypes.func.isRequired,
  serviceUri: PropTypes.string.isRequired,
  getRequest: PropTypes.func.isRequired,
  id: PropTypes.string,
  title: PropTypes.any,
  text: PropTypes.any,
  titleTooltip: PropTypes.string,
  textTooltip: PropTypes.string,
  detailEnabled: PropTypes.bool,
  detailIcon: PropTypes.string,
  detailIconTitle: PropTypes.string,
  onClick: PropTypes.func,
  onDetailClick: PropTypes.func,
  noGeometryIconTitle: PropTypes.string,
  onPolygonFoundForItem: PropTypes.func,
  item: PropTypes.object,
  onHover: PropTypes.func,
  index: PropTypes.number,
  selected: PropTypes.bool,
  active: PropTypes.bool,
  useSpecialButton: PropTypes.bool,
  specialButtonIcon: PropTypes.string,
  specialButtonTitle: PropTypes.string,
  onSpecialButtonClick: PropTypes.func,
  localization: PropTypes.shape({
    detailIconTitle: PropTypes.string.isRequired
  }).isRequired,
}
```
- **onSetHoverObject** - function to be called when hovered over (parameter is the underlaying item)
- **serviceUri** - url for getting detail information about certain items. Only for specialized usage inside twigis
- **getRequest** - get request for getting detail information about certain items. Only for specialized usage inside twigis
- **id** - html id of the default "ObjectCard" <a> element
- **title** - first line of text. However, can also be any html element instead of a string
- **text** - second line of text. However, can also be any html element instead of a string
- **titleTooltip** - tooltil for the first line of text
- **textTooltip** - tooltil for the second line of text
- **detailEnabled** - if false, the detail icon is hidden. Default value is true
- **detailIcon** - name of the 'detail' icon, see [Icon](#icon) for possible choices. Default value is 'info'
- **detailIconTitle** - tooltip for the 'detail' icon
- **onClick** - function to be called after the whole ObjectCard is clicked
- **onDetailClick** - function to be called after the 'detail' icon is clicked
- **noGeometryIconTitle** - tooltip for the 'no geometry' icon, which appears if the underlaying item has *geometry* property null or undefined
- **onPolygonFoundForItem** - function to be called after certain items' data are acquired. Only for specialized usage inside twigis
- **item** - underlaying item
- **onHover** - function to be called when hovered over (parameter is an index of the underlaying item)
- **index** - index of the underlaying item
- **selected** - if true, the style of the ObjectCard changes to indicate selection state
- **active** - if true, the style of the ObjectCard changes to indicate active state
- **useSpecialButton** - if true, additional 'special' icon appears (next to the 'detail icon')
- **specialButtonIcon** - name of the 'special' icon, see [Icon](#icon) for possible choices
- **specialButtonTitle** - tooltil for the 'special' icon
- **onSpecialButtonClick** - function to be called after the 'special' icon is clicked
- **localization** - object with localized strings

**Usage**:
``` javascript
state = {
    fulltextResults: [],
    fulltextInput: '',
    fulltextSelectedItem: null
}

fulltextPageChanged() {
    // TODO
}

fulltextSelectItem(item) {
    this.setState({
        fulltextSelectedItem: item
    });
}

copyToClipboard(copyText){
    if (navigator.clipboard && window.isSecureContext) {
        navigator.clipboard.writeText(copyText)
    }
}

render() {
    let localizationSearch = this.props.appInterface.tree.select('workspace', 'localization', 'search').get();
    let localizationDetails = this.props.appInterface.tree.select('workspace', 'localization', 'details').get();

    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <div className="Property">
                    <h4 className="Property-label">{"Results"}</h4>
                    <Paginator pageChanged={this.fulltextPageChanged.bind(this)} range={10} localization={localizationDetails}>
                        {this.state.fulltextResults.map((item, index) => {
                            <ObjectCard
                                localization={localizationSearch}
                                key={index}
                                index={index}
                                title={item.caption}
                                text={item.description}                
                                onSetHoverObject={(items) => this.props.appInterface.actions.setHoverObject(items)}
                                item={item}
                                active={this.state.fulltextSelectedItem == item}
                                onClick={this.fulltextSelectItem.bind(this, item)}
                                detailEnabled={false}
                                useSpecialButton={true}
                                specialButtonIcon="copy"
                                specialButtonTitle={localizationSearch.copyIconTitle}
                                onSpecialButtonClick={this.copyToClipboard.bind(this, item.copyText)}
                            />
                        })}
                    </Paginator>
                </div>
            </div>
        </div>
    )
}
```

[Back](#components)

#### OrderableList
**Description**: List of orderable items (for example steps). These can be rearranged or changed.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/orderablelist1.png?raw=true)

**Props**:
```
OrderableList.propTypes = {
  isInitiallyOpen: PropTypes.bool,
  zIndex: PropTypes.number,
  header: PropTypes.string,
  highlightHeader: PropTypes.bool,
  translator: PropTypes.shape({
    root: PropTypes.object.isRequired,
    details: PropTypes.shape({
      relation: PropTypes.shape({
        showInGrid: PropTypes.string.isRequired,
        showInMap: PropTypes.string.isRequired,
        noData: PropTypes.string.isRequired
      }).isRequired,
      loadNext: PropTypes.string.isRequired,
      loadNextMiddleWord: PropTypes.string.isRequired
    }).isRequired,
    search: PropTypes.shape({
      detailIconTitle: PropTypes.string.isRequired
    }).isRequired,
    validator: PropTypes.object.isRequired,
    editing: PropTypes.shape({
      removeItem: PropTypes.string.isRequired,
      nextStep: PropTypes.string.isRequired
    }).isRequired
  }).isRequired,
  totalCount: PropTypes.number,
  fields: PropTypes.arrayOf(PropTypes.shape({
    caption: PropTypes.string.isRequired,
    itemPropertyName: PropTypes.string.isRequired,
    type: PropTypes.oneOf(['string', 'number']),
    isRequired: PropTypes.bool
  })).isRequired,
  items: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    isAdded: PropTypes.bool,
    isRemoved: PropTypes.bool
  })).isRequired,
  onItemValueChanged: PropTypes.func,
  onRemoveItemClick: PropTypes.func,
  onUndoRemoveItemClick: PropTypes.func,
  onItemOrderChanged: PropTypes.func,
  onAddItemClick: PropTypes.func
}
```
- **isInitiallyOpen** - if false, the component is collapsed after loaded. Default value is false
- **zIndex** - z order of the control. Useful mainly in the situation when there are two OrderableLists above each other and their popups would interfere
- **header** - title of the component
- **highlightHeader** - if true, the header is visually highlighted
- **translator** - object with localized strings
- **totalCount** - total number of items
- **fields** - definition of the properties of the items. Array of items in this format: {caption: string, itemPropertyName: string, type: 'string'|'number', isRequired: bool}
- **items** - array of items. These should have at least *id* property and properties defined in the *fields* prop
- **onItemValueChanged** - function called when property of an item was changed
- **onRemoveItemClick** - function called when an item is issued to be removed
- **onUndoRemoveItemClick** - function called when an item is issued to be returned back after being removed
- **onItemOrderChanged** - function called when an item is issued to change it's order
- **onAddItemClick** - function called when a new item is issued to be created

**Usage**:
``` javascript
state = {
    loadedSteps: {
        [-1]: {
            name: 'Preparation',
            id: -1,
            description: 'Prepare your tools',
            duration: 5
        },
        [-2]: {
            name: 'Paints',
            id: -2,
            description: 'Mix suitable colors',
            duration: 15
        }
    },
    stepsOrder: [-1, -2],
    addedSteps: {},
    removedSteps: {}
}

this.lastId = 0;

getNextId() {
   this.lastId++;
   return this.lastId;
}

handleStepPropertyChanged(item, prop, value) {
    if(item.isAdded) {
        let newAddedSteps = JSON.parse(JSON.stringify(this.state.addedSteps));
        newAddedSteps[item.id][prop.itemPropertyName] = value;
        this.setState({
            addedSteps: newAddedSteps
        });
    }
    else {
        let newLoadedSteps = JSON.parse(JSON.stringify(this.state.loadedSteps));
        newLoadedSteps[item.id][prop.itemPropertyName] = value;
        this.setState({
            loadedSteps: newLoadedSteps
        });
    }
}

handleStepRemoved(item) {
    if(item.isAdded) {
        let oldIndex = this.state.stepsOrder.indexOf(item.id);
        let newOrder = [...this.state.stepsOrder];
        newOrder.splice(oldIndex, 1);
        let newAddedSteps = JSON.parse(JSON.stringify(this.state.addedSteps));
        newAddedSteps[item.id] = undefined;
        this.setState({
            stepsOrder: newOrder,
            addedSteps: newAddedSteps
        });
    }
    else {
        let newRemovedSteps = JSON.parse(JSON.stringify(this.state.removedSteps));
        newRemovedSteps[item.id] = item;
        this.setState({
            removedSteps: newRemovedSteps
        });
    }
}

handleStepUndoRemoved(item) {
    let newRemovedSteps = JSON.parse(JSON.stringify(this.state.removedSteps));
    newRemovedSteps[item.id] = undefined;
    this.setState({
        removedSteps: newRemovedSteps
    });
}

handleStepsOrderChanged(item, newIndex) {
    let oldIndex = this.state.stepsOrder.indexOf(item.id);
    if(oldIndex != newIndex) {
        let newOrder = [...this.state.stepsOrder];
        newOrder.splice(oldIndex, 1);
        newOrder.splice(newIndex, 0, item.id);
        this.setState({
            stepsOrder: newOrder
        });
    }
}

handleAddNewStep() {
    let newId = this.getNextId();
    let newAddedSteps = JSON.parse(JSON.stringify(this.state.addedSteps));
    newAddedSteps[newId] = {
        name: '',
        id: newId,
        description: '',
        duration: 0
    };
    let newOrder = [...this.state.stepsOrder];
    newOrder.push(newId);
    this.setState({
        addedSteps: newAddedSteps,
        stepsOrder: newOrder
    });
}

render() {
    let fields = [
        {
            caption: "Name",
            itemPropertyName: 'name',
            type: 'string',
            isRequired: true
        },
        {
            caption: "Description",
            itemPropertyName: 'description',
            type: 'string',
            isRequired: false
        },
        {
            caption: "Duration",
            itemPropertyName: 'duration',
            type: 'number',
            isRequired: false
        }
    ];
    let steps = [];
    for(let id of this.state.stepsOrder) {
        if(this.state.loadedSteps[id]) {
            let item = this.state.loadedSteps[id];
            steps.push({
                id: item.id,
                name: item.name,
                dataendpoint: item.id,
                description: item.description,
                duration: item.duration,
                isAdded: false,
                isRemoved: Boolean(this.state.removedSteps[id])
            });
        }
        else if(this.state.addedSteps[id]) {
            let item = this.state.addedSteps[id];
            steps.push({
                id: item.id,
                name: item.name,
                dataendpoint: item.id,
                description: item.description,
                duration: item.duration,
                isAdded: true,
                isRemoved: false
            });
        }
    }

    let orderableListTranslator = this.props.appInterface.tree.select('workspace', 'localization').get();
    
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <OrderableList
                    header={"Steps"}
                    isInitiallyOpen={true}
                    zIndex={2}
                    translator={orderableListTranslator}
                    totalCount={steps.length}
                    fields={fields}
                    items={steps}
                    onItemValueChanged={this.handleStepPropertyChanged.bind(this)}
                    onRemoveItemClick={this.handleStepRemoved.bind(this)}
                    onItemOrderChanged={this.handleStepsOrderChanged.bind(this)}
                    onAddItemClick={this.handleAddNewStep.bind(this)}
                    onUndoRemoveItemClick={this.handleStepUndoRemoved.bind(this)}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### Overlay
**Description**: Transparently gray or white curtain, mainly purposed as a disabled indicator for any content.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/overlay1.png?raw=true)

**Props**:
```
Overlay.propTypes = {
  enabled: PropTypes.bool,
  className: PropTypes.string,
  hideSpinner: PropTypes.bool
}
```
- **enabled** - if false, the component is hidden. Default value is false
- **className** - additional class that appends the default "Overlay" class
- **hideSpinner** - if true, no spinner is displayed. Default value is false

**Usage**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                ...
            </div>
            <Overlay enabled={this.state.workInProgress} hideSpinner={true}/>
        </div>
    )
}
```
[Back](#components)

#### Paginator
**Description**: Vertical list of items, that provides division into pages.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/paginator1.png?raw=true)

**Props**:
```
Paginator.propTypes = {
  range: PropTypes.number,
  pageChanged: PropTypes.func,
  children: PropTypes.array,
  localization: PropTypes.shape({
    paginatorNavigationPrefix: PropTypes.string.isRequired
  }).isRequired
}
```
- **range** - number of items per page
- **pageChanged**
- **children** - items, content of the pages
- **localization** - object with localized strings

**Usage**:
``` javascript
state = {
    fulltextResults: [],
    fulltextInput: '',
    fulltextSelectedItem: null
}

fulltextPageChanged() {
    // TODO
}

fulltextSelectItem(item) {
    this.setState({
        fulltextSelectedItem: item
    });
}

openDetail(item) {
    // TODO
}

render() {
    let localizationSearch = this.props.appInterface.tree.select('workspace', 'localization', 'search').get();
    let localizationDetails = this.props.appInterface.tree.select('workspace', 'localization', 'details').get();

    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <div className="Property">
                    <h4 className="Property-label">{"Results"}</h4>
                    <Paginator pageChanged={this.fulltextPageChanged.bind(this)} range={10} localization={localizationDetails}>
                        {this.state.fulltextResults.map((item, index) => {
                            <div className="mySearchedItem" key={index}>
                                <ObjectCard
                                    key={index}
                                    index={index}
                                    title={<TextHighlight phrase={this.state.fulltextInput}>{item.caption}</TextHighlight>}
                                    text={<TextHighlight phrase={this.state.fulltextInput}>{item.description}</TextHighlight>}
                                    active={this.state.fulltextSelectedItem == item}
                                    onClick={this.fulltextSelectItem.bind(this, item)}
                                    onDetailClick={this.openDetail.bind(this, item)}
                                    onSetHoverObject={(items) => this.props.appInterface.actions.setHoverObject(items)}
                                    item={item}
                                    detailIconTitle={localizationSearch.detailIconTitle}
                                    noGeometryIconTitle={localizationSearch.noGeometryIconTitle}
                                    localization={localizationSearch} />
                            </div>
                        })}
                    </Paginator>
                </div>
            </div>
        </div>
    )
}
```
[Back](#components)

#### ProgressBar
**Description**: Percentage indicator of progress (or something else).

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/progressbar1.png?raw=true)

**Props**:
```
ProgressBar.propTypes = {
  progress: PropTypes.number,
  showNumber: PropTypes.bool
}
```
- **progress** - value of progress between 0 and 100
- **showNumber** - if true, the percentage is also displayed as a small number in the middle of a progress bar

**Usage**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <ProgressBar progress={this.state.workProgressPercentage} showNumber={true}></ProgressBar>
            </div>
        </div>
    )
}
```
[Back](#components)

#### Property
**Description**: Basic block for attribute name and attribute value.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/property1.png?raw=true)

**Props**:
```
Property.propTypes = {
  label: PropTypes.any,
  value: PropTypes.any,
  isIE: PropTypes.bool,
  children: PropTypes.any,
  isForEditing: PropTypes.bool,
  valueClassName: PropTypes.string
}
```
- **label** - header or attribute name
- **value** - attribute value
- **isIE** - *deprecated*
- **children** - voluntary collection of html elements to be displayed inside a property
- **isForEditing** - if true, changes a color of the label (from black to gray)
- **valueClassName** - additional class that appends the default "Property-value" class

**Usage**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <Property label={"Priority"} value={this.state.currentItem.priority} />
            </div>
        </div>
    )
}
```
[Back](#components)

#### PropertyAppLink
**Description**: Basic block for attribute name and attribute value as hyperlink.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/propertylink1.png?raw=true)

**Props**:
```
PropertyAppLink.propTypes = {
  label: PropTypes.any.isRequired,
  title: PropTypes.string,
  settings: PropTypes.shape({
    action: PropTypes.string.isRequired,
    parameters: PropTypes.object.isRequired
  }).isRequired,
  router: PropTypes.object,
  grid: PropTypes.object,
  gridSettings: PropTypes.object,
  onClick: PropTypes.func,
  caption: PropTypes.string,
  getEntityNameFunction: PropTypes.func.isRequired,
  gridFetchSpecsForEntityFunction: PropTypes.func.isRequired,
  resolveActionFunction: PropTypes.func.isRequired,
  replaceParamsFunction: PropTypes.func.isRequired,
  appLinkTypes: PropTypes.shape({
    EXTERNAL_URI: PropTypes.string.isRequired,
    SHOW_GRID: PropTypes.string.isRequired,
    SHOW_MODULE: PropTypes.string.isRequired,
    SHOW_DETAIL: PropTypes.string.isRequired
  })
}
```
- **label** - hyperlink text
- **title** - title for a new panel (if the result of clicking the hyperlink is opening a new panel)
- **settings** - special object that can define behavior of the hyperlink (corresponds to the settings via twigis administration). It should have *action* and *parameters* properties. The *action* property can be one of following types: 'ExternalURI', 'ShowGrid', 'ShowModule', 'ShowDetail', 'ShowReport' or 'ShowPanelURI'. If the value is 'ShowDetail', then the *parameters* property requires additional *dataendpoint* sub-property. If the value is 'ExternalURI', then the *parameters* property requires additional *uri* sub-property
- **router** - router object for managing browser navigation. Value passed by the DataGrid parent object
- **grid** - for internal use only
- **gridSettings** - object representing current grid settings. Value passed by the DataGrid parent object
- **onClick** - function called after the default action is performed (defined via settings)
- **caption** - header or attribute name
- **getEntityNameFunction** - function for parsing an entity name from a dataendpoint
- **gridFetchSpecsForEntityFunction** - function for reading the specs of a certain entity
- **resolveActionFunction** - function for managing the default behavior of the action defined via twigis administration
- **replaceParamsFunction** - helper function for *resolveActionFunction*
- **appLinkTypes**

**Usage 1 - self handling**:
``` javascript
render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <PropertyAppLink label={"Open"} 
                    caption={"Contracts"} 
                    settings={{action: '', parameters: {}}} 
                    onClick={this.handleDisplayContractsClick.bind(this)} 
                    getEntityNameFunction={this.props.appInterface.api.getEntityName} 
                    gridFetchSpecsForEntityFunction={this.props.appInterface.actions.gridFetchSpecsForEntity} 
                    resolveActionFunction={this.props.appInterface.utils.resolveAction} 
                    replaceParamsFunction={this.props.appInterface.utils.replaceParams} 
                    appLinkTypes={this.props.appInterface.constants.AppLinkTypes} />
            </div>
        </div>
    )
}
```

**Usage 2 - open filtered grid**:
``` javascript
render() {
    let openMyOpenedTicketsSettings = {
        action: 'ShowGrid',
        parameters: {
            dataentity: 'DS/TICKETS',
            filter: `@FID_USER eq ${this.myUserId} and @STATE ne 'closed'`
        }
    };

    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <PropertyAppLink label={"Open"} 
                    caption={"My opened tickets"} 
                    title={"My opened tickets"}
                    settings={openMyOpenedTicketsSettings}
                    getEntityNameFunction={this.props.appInterface.api.getEntityName} 
                    gridFetchSpecsForEntityFunction={this.props.appInterface.actions.gridFetchSpecsForEntity} 
                    resolveActionFunction={this.props.appInterface.utils.resolveAction} 
                    replaceParamsFunction={this.props.appInterface.utils.replaceParams} 
                    appLinkTypes={this.props.appInterface.constants.AppLinkTypes} />
            </div>
        </div>
    )
}
```
[Back](#components)

#### RadioButton
**Description**: Theme-styled radio button.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/radiobutton1.png?raw=true)

**Props**:
```
RadioButton= {
  name: PropTypes.string,
  suffix: PropTypes.any,
  checked: PropTypes.bool,
  disabled: PropTypes.bool,
  value: PropTypes.any,
  onChange: PropTypes.func
}
```
- **name** - voluntary identifier of the input. Unique name is composed as '{name}-{suffix}'
- **suffix** - voluntary identifier of the input. Unique name is composed as '{name}-{suffix}'
- **checked** - state of the radio-button
- **disabled** - if true, radio-button is visible, but grayed-out and unable to be changed
- **value** - background representation of the radio-button value (important for multiple radio-buttons at the same place)
- **onChange** - `currently unreliable event - we recommend wrapping RadioButton in a div and handle the *onClick* event of that div`

**Usage**:
``` javascript
handleRadioValueChanged(value) {
    this.setState({
        radioValue: value
    });
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <div style={{position: 'relative', display: 'block', margin: '10px 0', padding: '0 0 10px 0'}} 
                    onClick={this.handleRadioValueChanged.bind(this, 1)}>
                    <RadioButton name="value" 
                        suffix="1" 
                        checked={this.state.radioValue == 1} 
                        value={1} 
                        onChange={this.handleRadioValueChanged.bind(this, 1)}/>
                    <span>Choice 1</span>
                </div>
                <div style={{position: 'relative', display: 'block', margin: '10px 0', padding: '0 0 10px 0'}} 
                    onClick={this.handleRadioValueChanged.bind(this, 2)}>
                    <RadioButton name="value" 
                        suffix="2" 
                        checked={this.state.radioValue == 2} 
                        value={2} 
                        onChange={this.handleRadioValueChanged.bind(this, 2)}/>
                    <span>Choice 2</span>
                </div>
            </div>
        </div>
    )
}
```
[Back](#components)

#### RelationControl
**Description**: Control for displaying multiple items. Main purpose is to display relations 1:N or M:N. Includes capability for lazy loading displayed items.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/relationcontrol1.png?raw=true)

**Props**:
```
RelationControl.propTypes = {
  identifier: PropTypes.any,
  header: PropTypes.string,
  items: PropTypes.arrayOf(PropTypes.shape({
    caption: PropTypes.string.isRequired,
    dataendpoint: PropTypes.string.isRequired,
    isDim: PropTypes.bool,
    isImportant: PropTypes.bool,
    isChecked: PropTypes.bool
  })),
  totalCount: PropTypes.number,
  currentCount: PropTypes.number,
  onLoadNextClick: PropTypes.func,
  onDetailClick: PropTypes.func,
  zIndex: PropTypes.number,
  onAttachNewItemClick: ProprTypes.func,
  onShowInGridClick: PropTypes.func,
  onShowInMapClick: PropTypes.func,
  isInitiallyOpen: PropTypes.bool,
  hideAttachNewItemAction: PropTypes.bool,
  hideShowInGridAction: PropTypes.bool,
  hideShowInMapAction: PropTypes.bool,
  highlightedItems: PropTypes.object,
  highlightHeader: PropTypes.bool,
  onOpenOutsideWindow: PropTypes.func,
  translator: PropTypes.shape({
    root: PropTypes.object.isRequired,
    details: PropTypes.shape({
      relation: PropTypes.shape({
        showInGrid: PropTypes.string.isRequired,
        showInMap: PropTypes.string.isRequired,
        noData: PropTypes.string.isRequired
      }).isRequired,
      loadNext: PropTypes.string.isRequired,
      loadNextMiddleWord: PropTypes.string.isRequired
    }).isRequired,
    search: PropTypes.shape({
      detailIconTitle: PropTypes.string.isRequired
    }).isRequired
  }).isRequired,
  onRowMouseEnter: PropTypes.func,
  onRowMouseLeave: PropTypes.func,
  disableAttachNewItemAction: PropTypes.bool,
  disableShowInGridAction: PropTypes.bool,
  disableShowInMapAction: PropTypes.bool,
  isIconDisabled: PropTypes.bool,
  enableItemChecking: PropTypes.bool,
  onItemCheckChanged: PropTypes.func,
  extraItemButtons: PropTypes.arrayOf(
    PropTypes.shape({
      icon: PropTypes.string.isRequired,
      iconTitle: PropTypes.string,
      click: PropTypes.func
    })
  ),
  extraMenuActions: PropTypes.arrayOf(
    PropTypes.shape({
      title: PropTypes.string.isRequired,
      icon: PropTypes.string.isRequired,
      condition: PropTypes.bool,
      disabled: PropTypes.bool,
      click: PropTypes.func
    })
  )
}
```
- **identifier** - unique identifier of the relation control. Useful when handling more relation controls' events with one common handler
- **header** - caption (header) of the control
- **items** - collection of items to be displayed. They need to have the *caption* and *dataendpoint* properties (dataendpoint should be a unique identifier). Voluntarily they can have other properties which effect the styling - *isDim*, *isImportant* and also property *isChecked* which is used with *enableItemChecking* set to true
- **totalCount** - number of all the items (including not yet loaded items)
- **currentCount** - number of loaded items
- **onLoadNextClick** - function to be called after the link for loading next items is clicked
- **onDetailClick** - function to be called after the info icon on an item is clicked
- **zIndex** - z order of the control. Useful mainly in the situation when there are two RelationControls above each other and their popups would interfere
- **onAttachNewItemClick** - function to be called after the action menu item for adding a new item is clicked
- **onShowInGridClick** - function to be called after the action menu item for showing all the items in a grid is clicked
- **onShowInMapClick** - function to be called after the action menu item for showing all the items in a map is clicked
- **isInitiallyOpen** - if false, the list of items is collapsed on the first render
- **hideAttachNewItemAction** - if true, the action menu item for adding a new item is not displayed
- **hideShowInGridAction** - if true, the action menu item for showing all the items in a grid is not displayed
- **hideShowInMapAction** - if true, the action menu item for showing all the items in a map is not displayed
- **highlightedItems** - object representing highlighted items - each property with the same name as item's dataendpoint that is set to true represents that item to be highlighted
- **highlightHeader** - if true, the relation control header is highlighted
- **onOpenOutsideWindow** - function for handling scrolling if the combo box and corresponding popup items would be opened outside of window view
- **translator** - object with localized strings
- **onRowMouseEnter** - function to be called after a mouse is hovered over one of the items
- **onRowMouseLeave** - function to be called after a mouse hover leaves one of the items
- **disableAttachNewItemAction** - if true, the action menu item for adding a new item is displayed, but grayed-out and unable to be clicked
- **disableShowInGridAction** - if true, the action menu item for showing all the items in a grid is displayed, but grayed-out and unable to be clicked
- **disableShowInMapAction** - if true, the action menu item for showing all the items in a map is displayed, but grayed-out and unable to be clicked
- **isIconDisabled** - if true, the info icon and the three dots menu are grayed-out and unable to be clicked
- **enableItemChecking** - if true, each item is preceded with a checkbox
- **onItemCheckChanged** - function to be called after an item is checked or unchecked
- **extraItemButtons** - array of extra buttons to appear next to the default info icon (for available types see [Icon](#icon))
- **extraMenuActions** - array of extra buttons to appear in the action menu (for available types see [Icon](#icon))

**Usage**:
``` javascript
const SECTION_ENTITY = 'ds/section';

state = {
    loadedSections: {
        items: [
            {id: 1, name: "Item1", dataendpoint: "ds/section/1"},
            {id: 2, name: "Item2", dataendpoint: "ds/section/2"},
            {id: 3, name: "Item3", dataendpoint: "ds/section/3"},
            {id: 4, name: "Item4", dataendpoint: "ds/section/4"},
            {id: 5, name: "Item5", dataendpoint: "ds/section/5"}
        ],
        total: 18
    }
};

loadNextSections() {
    if(this.state.loadedSections.items.length == this.state.loadedSections.total) {
        return;
    }
    // simulate loading data from database
    let newItems = [...this.state.loadedSections.items];
    let lastId = this.state.loadedSections.items[this.state.loadedSections.items.length - 1].id;
    lastId += 1;
    for(let i = 0; i < 5; i++) {
        newItems.push({
            id: lastId,
            name: `Item${lastId}`,
            dataendpoint: `${SECTION_ENTITY}/${lastId}`
        });
        if(newItems.length == this.state.loadedSections.total) {
            break;
        }
        lastId += 1;
    }
    this.setState({
        loadedSections: {
            items: newItems,
            total: this.state.loadedSections.total
        }
    });
}

openDetail(item) {
    this.props.appInterface.actions.openPanel({
        type: 'detail',
        title: null,
        description: null,
        config: {
            type: 1,
            data: null,
            endpoint: item.dataendpoint
        }
    });
}

handleShowSectionsInGridClick() {
    alert("TODO");
}

handleSectionRowMouseEnter(index, sections) {
    let item = sections[index];
    this.props.appInterface.actions.setHoverObject(item);
}

handleSectionRowMouseLeave() {
    this.props.appInterface.actions.setHoverObject(null);
}

render() {
    let sections = [];
    if(this.state.loadedSections && this.state.loadedSections.items && this.state.loadedSections.items.length) {
            for(let item of this.state.loadedSections.items) {
                sections.push({
                    id: item.id,
                    caption: item.name,
                    dataendpoint: item.dataendpoint,
                    description: item.name
                });
            }
        }
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <RelationControl identifier="sections"
                    header={"Sections"}
                    items={sections}
                    totalCount={this.state.loadedSections.total}
                    currentCount={sections.length}
                    zIndex={4}
                    isInitiallyOpen={true}
                    hideAttachNewItemAction={true}
                    hideShowInGridAction={false}
                    disableShowInGridAction={sections.length == 0}
                    hideShowInMapAction={true}
                    onDetailClick={this.openDetail.bind(this)}
                    onShowInGridClick={this.handleShowSectionsInGridClick.bind(this)}
                    onLoadNextClick={this.loadNextSections.bind(this)}
                    translator={this.props.appInterface.tree.select('workspace', 'localization').get()}
                    onRowMouseEnter={(index) => {this.handleSectionRowMouseEnter(index, sections)}}
                    onRowMouseLeave={() => {this.handleSectionRowMouseLeave()}}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### RoundSwitch
**Description**: Slider-like switch. Can be two-state switch (gray/green) or three-state switch (red/gray/green)

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/roundswitch1.png?raw=true)

**Props**:
```
RoundSwitch.propTypes = {
  value: PropTypes.bool,
  onClick: PropTypes.func,
  isThreeState: PropTypes.bool,
  text: PropTypes.string
}
```
- **value** - true, false or null value. Null value represents the "undefined" gray value in a three-state switch
- **onClick**
- **isThreeState** - if true, the mode of the switch is a three-state, otherwise it is a two-state
- **text** - text description next to the switch

**Usage**:
``` javascript
handleOverwriteExistingClick() {
    this.setState({
        overwriteExisting: !this.state.overwriteExisting
    });
}

render() {
    return (
        <RoundSwitch text={"Overwrite existing"}
            value={this.state.overwriteExisting}
            onClick={this.handleOverwriteExistingClick.bind(this)}
            isThreeState={false}/>
    )
}
```
[Back](#components)

#### RowToolsCell
**Description**: Special popup cell in a [DataGrid](#datagrid) bringing up some context actions.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/rowtoolscell1.png?raw=true)

**Props**:
```
RowToolsCell.propTypes = {
  offset: PropTypes.any,
  columnKey: PropTypes.string,
  rowIndex: PropTypes.number,
  data: PropTypes.array,
  router: PropTypes.object,
  grid: PropTypes.object,
  suppressClosingPanelOnSelectionInMap: PropTypes.bool,
  onDetailClick: PropTypes.func,
  onSelectionClick: PropTypes.func,
  updateGridHandler: PropTypes.func,
  onCloseEditPanel: PropTypes.func,
  onOpenEditPanel: PropTypes.func,
  selectionTypes: PropTypes.object.isRequired,
  closePanelFunction: PropTypes.func.isRequired,
  closeAllPanelsFunction: PropTypes.func.isRequired,
  localization: PropTypes.object.isRequired,
  hidePanelsFunction: PropTypes.func.isRequired,
  setFixedPanelsFunction: PropTypes.func.isRequired,
  setClickObjectFunction: PropTypes.func.isRequired,
  openPanelFunction: PropTypes.func.isRequired,
  resolveActionFunction: PropTypes.func.isRequired,
  replaceParamsFunction: PropTypes.func.isRequired
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### SearchField
**Description**: Text field with a button for clearing the input text. Quite similar to [ClearableInput](#clearableinput)

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/searchfield1.png?raw=true)

**Props**:
```
SearchField.propTypes = {
  text: PropTypes.string,
  onFilterChange: PropTypes.func,
  onClearClick: PropTypes.func,
  toolTip: PropTypes.string,
  placeholder: PropTypes.string
}
```
- **text** - the input text
- **onFilterChange** - function to be called after typed text has changed
- **onClearClick** - function to be called after the 'clear' button was clicked
- **toolTip** - tooltip of the input
- **placeholder** - placeholder (text for empty value) of the input

**Usage**:
``` javascript
handleSearchTextChanged(value) {
    this.setState({
        searchText: value
    });
}

handleClearSearchTextClick() {
    this.setState({
        searchText: ''
    });
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <SearchField text={this.state.searchText}
                    onFilterChange={this.handleSearchTextChanged.bind(this)}
                    onClearClick={this.handleClearSearchTextClick.bind(this)}
                    toolTip={"Text to search for"}
                    placeholder={"Enter text"}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

#### searchFieldSelect
**Description**: Combo box with added functionality of search mechanism among possible values.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/searchselectfield1.png.png?raw=true)

**Props**:
```
searchFieldSelect.propTypes = {
  onFocus: PropTypes.func,
  onBlur: PropTypes.func,
  resetWindowScrollAfterFocus: PropTypes.bool,
  isEnumeration: PropTypes.bool,
  empty: PropTypes.bool,
  value: PropTypes.any,
  type: PropTypes.string,
  name: PropTypes.string,
  options: PropTypes.array,
  hideEmptyRecord: PropTypes.bool,
  disabled: PropTypes.bool,
  isLight: PropTypes.bool,
  hideSearchField: PropTypes.bool,
  disableScrollbar: PropTypes.bool,
  onOpenOutsideWindow: PropTypes.func,
  handleScrollEnd: PropTypes.func,
  outsideFiltering: PropTypes.bool,
  onFilterChange: PropTypes.func,
  onToggle: PropTypes.func,
  awaitingData: PropTypes.bool,
  clearFilterAfterSelection: PropTypes.bool,
  dontAdoptValueAfterSelection: PropTypes.bool,
  disableScrollIntoView: PropTypes.bool,
  valueChangedFromPropsCausesChange: PropTypes.bool,
  localization: PropTypes.shape({
    searchSelectPlaceholder: PropTypes.string.isRequired,
    editing: PropTypes.shape({
      emptyComboValue: PropTypes.string.isRequired
    }).isRequired,
    grid: PropTypes.shape({
      filterPlaceHolderSearch: PropTypes.string.isRequired
    }).isRequired,
    searchSelect: PropTypes.shape({
      noItems: PropTypes.string.isRequired
    }).isRequired
  }).isRequired,
  onFocusSearchSelect: PropTypes.func,
  devicesConfig: PropTypes.shape({
    viewPorts: PropTypes.shape({
      MOBIL: PropTypes.shape({
        max: PropTypes.number.isRequired
      }).isRequired,
      TABLET: PropTypes.shape({
        min: PropTypes.number.isRequired, 
        max: PropTypes.number.isRequired
      }).isRequired,
      PC: PropTypes.shape({
        min: PropTypes.number.isRequired
      }).isRequired
    }).isRequired,
    devices: PropTypes.shape({
      MOBIL: PropTypes.string.isRequired,
      TABLET: PropTypes.string.isRequired,
      PC: PropTypes.string.isRequired
    }).isRequired
  })
}
```
- **onFocus**
- **onBlur**
- **resetWindowScrollAfterFocus** - if true, the combo box will be scrolled into view after focused outside of view
- **isEnumeration** - better handling on mobile devices. Should be true in most cases
- **empty** - if true, the field is considered empty (not filled in) despite value
- **value** - numeric (or string) representation of the selected item (unique key) or null
- **type** - data type of the items key - can be 'Number' or 'String'
- **name** - voluntary identifier of the field
- **options** - array of items to be chosen from in format {id: Number|String, value: String}
- **hideEmptyRecord** - if true, the *null* placeholder item will not be included
- **disabled** - if true, combo is visible, but grayed-out and unable to be changed
- **isLight** - if true, the default color for texts changes from black to gray
- **hideSearchField** - if true, the search functionality will disappear
- **disableScrollbar** - if true, all the items will be visible without any max count and without any scrollbar
- **onOpenOutsideWindow** - function for handling scrolling if the combo box and corresponding popup items would be opened outside of window view
- **handleScrollEnd** - function to be called after list of items is scrolled to the bottom
- **outsideFiltering** - if true, default filtering (by searched text) will be disabled
- **onFilterChange** - function called after searched text has changed
- **onToggle** - function called after itemlist popup or dismissed
- **awaitingData** - if true, combo indicates, that items are not yet loaded
- **clearFilterAfterSelection** - if true, searched text is cleared after an item is selected
- **dontAdoptValueAfterSelection** - if true, when an item is selected, the *onChange* event is fired but the item is not internally set as selected
- **disableScrollIntoView** - if true, the combo is not moved into view after toggle
- **valueChangedFromPropsCausesChange** - if true, event the value set from props (and not internally) will indicate change
- **localization** - object with localized strings
- **onFocusSearchSelect** - function called after an input of searched text is focused
- **devicesConfig**

**Usage**:
``` javascript
var defaultDevicesConfig = null;

this.prioritiesEnum = [
    {id: 1, value: 'Low'},
    {id: 2, value: 'Normal'},
    {id: 3, value: 'High'}
];

componentDidMount() {
    this.ensureDefaultDevicesConfig(this.props);
}

ensureDefaultDevicesConfig(props) {
    if(!defaultDevicesConfig && props.appInterface && props.appInterface.constants) {
        defaultDevicesConfig = {
            viewPorts: props.appInterface.constants.MapControlTypes.VIEWPORTS,
            devices: props.appInterface.constants.MapControlTypes.Devices
        };
    }
}

onComboOpenedOutsideOfWindow(event) {
    let scrollTop = 0;
    let closestPanelContent = this.props.appInterface.utils.findClosestAncestorById(this.form, 'Panel-content');
    let closestFieldToTarget = null;
    if (event.target) {
        closestFieldToTarget = this.props.appInterface.utils.findClosestAncestorByClassName(event.target, 'Field');
    }
    if (closestFieldToTarget) {
        scrollTop += closestFieldToTarget.offsetTop;
    }
    let closestGroupFieldToTarget = null;
    if (event.target) {
        closestGroupFieldToTarget = this.props.appInterface.utils.findClosestAncestorByClassName(event.target, 'GroupField');
    }
    if (closestGroupFieldToTarget) {
        scrollTop += closestGroupFieldToTarget.offsetTop;
    }
    if (closestPanelContent) {
        closestPanelContent.scrollTop = scrollTop ? scrollTop : closestPanelContent.scrollHeight;
    }
}

handlePriorityChanged(args) {
    for(let item of this.prioritiesEnum) {
        if(item.id === args) {
            this.setState({
                priority: item
            });
            break;
        }
    }
}

render() {
    let localizationSearchSelect = this.props.appInterface.tree.select('workspace', 'localization', 'searchSelect').get();
    let localizationValidator = this.props.appInterface.tree.select('workspace', 'localization', 'validator').get();
    let localizationSelectField = {...this.props.appInterface.tree.select('workspace', 'localization').get(), ...localizationSearchSelect, ...localizationValidator};
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <searchFieldSelect label={"Priority"} 
                    options={this.prioritiesEnum} 
                    value={this.state.priority ? this.state.priority.id : null} 
                    empty={this.state.priority === null || this.state.priority === undefined} 
                    resetWindowScrollAfterFocus={true} 
                    className='Field-enumeration' 
                    isEnumeration={true} 
                    hideEmptyRecord={true} 
                    type='Number' 
                    outsideFiltering={false} 
                    localization={localizationSelectField} 
                    required={true} 
                    onChange={this.handlePriorityChanged.bind(this)} 
                    onOpenOutsideWindow={this.onComboOpenedOutsideOfWindow.bind(this)} 
                    devicesConfig={defaultDevicesConfig}
                    onFocusSearchSelect={this.props.appInterface.actions.focusSearchSelect}/>
            </div>
        </div>
    )
}
```
[Back](#components)

#### SearchSelect
**Description**: Searchable combo box. Main purpose is as an inner component for [searchFieldSelect](#searchfieldselect), but can be also used separately.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/searchselect1.png?raw=true)

**Props**:
```
SearchSelect.propTypes = {
  defaultValue: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number
  ]),
  options: PropTypes.arrayOf(PropTypes.shape({
    text: PropTypes.string.isRequired | null,
    value: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.number
    ])
  })),
  disabled: PropTypes.bool,
  placeholder: PropTypes.string,
  onToggle: PropTypes.func,
  hideOnBlur: PropTypes.bool,
  showAllOptions: PropTypes.bool,
  onChange: PropTypes.func,
  onFilterChange: PropTypes.func,
  onBlurFilter: PropTypes.func,
  onFocusFilter: PropTypes.func,
  typeOfOptions: PropTypes.string,
  initialFilterValue: PropTypes.string,
  isEnumeration: PropTypes.bool,
  onOpenOutsideWindow: PropTypes.func,
  isWithoutArrowDown: PropTypes.bool,
  showNoItemsMsg: PropTypes.bool,
  isLight: PropTypes.bool,
  disableScrollbar: PropTypes.bool,
  className: PropTypes.string,
  hideSearchField: PropTypes.bool,
  disableTextHighlighting: PropTypes.bool,
  handleScrollEnd: PropTypes.func,
  outsideFiltering: PropTypes.bool,
  awaitingData: PropTypes.bool,
  clearFilterAfterSelection: PropTypes.bool,
  disableScrollIntoView: PropTypes.bool,
  devicesConfig: PropTypes.shape({
    viewPorts: PropTypes.shape({
      MOBIL: PropTypes.shape({
        max: PropTypes.number.isRequired
      }).isRequired,
      TABLET: PropTypes.shape({
        min: PropTypes.number.isRequired, 
        max: PropTypes.number.isRequired
      }).isRequired,
      PC: PropTypes.shape({
        min: PropTypes.number.isRequired
      }).isRequired
    }).isRequired,
    devices: PropTypes.shape({
      MOBIL: PropTypes.string.isRequired,
      TABLET: PropTypes.string.isRequired,
      PC: PropTypes.string.isRequired
    }).isRequired
  }),
  localization: PropTypes.shape({
    editing: PropTypes.shape({
      emptyComboValue: PropTypes.string.isRequired
    }).isRequired,
    grid: PropTypes.shape({
      filterPlaceHolderSearch: PropTypes.string.isRequired
    }).isRequired,
    searchSelect: PropTypes.shape({
      noItems: PropTypes.string.isRequired
    }).isRequired
  }).isRequired
}
```
- **defaultValue** - defines the initial selected item (option)
- **options** - array of items to be selected from
- **disabled** - if true, combo is visible, but grayed-out and unable to be clicked
- **placeholder** - placeholder (text for empty value) of the input
- **onToggle** - function to be called after the dropdown appears or disappears
- **hideOnBlur** - if true, the dropdown disappears after losing focus
- **showAllOptions** - if true, all the options are part of the dropdown even if filter would make them disappear
- **onChange** - function to be called after an item (from available options) was chosen as active
- **onFilterChange** - function to be called after filtering text was changed
- **onBlurFilter** - function to be called after filtering input lost focus
- **onFocusFilter** - function to be called after filtering input gained focus
- **typeOfOptions** - defines if the items in the *options* prop are identified by number or string. Can be 'number' or 'string'
- **initialFilterValue** - defines the initial filtering text
- **isEnumeration** - better handling on mobile devices. Should be true in most cases
- **onOpenOutsideWindow** - function for handling scrolling if the combo box and corresponding popup items would be opened outside of window view
- **isWithoutArrowDown** - legacy property, not to be used anymore
- **showNoItemsMsg** - if true and there are no items to be shown (either at all or filtered out), a special message appears
- **isLight** - if true, the default color for texts changes from black to gray
- **disableScrollbar** - if true, all the items will be visible without any max count and without any scrollbar
- **className** - additional class that appends the top <div> element
- **hideSearchField** - if true, the search functionality will disappear
- **disableTextHighlighting** - if true, when filtering items, there will be no text highlighting of typed text
- **handleScrollEnd** - function to be called after list of items is scrolled to the bottom
- **outsideFiltering** - if true, default filtering (by searched text) will be disabled
- **awaitingData** - if true, combo indicates, that items are not yet loaded
- **clearFilterAfterSelection** - if true, searched text is cleared after an item is selected
- **disableScrollIntoView** - if true, the combo is not moved into view after toggle
- **devicesConfig**
- **localization** - object with localized strings

**Usage**:
``` javascript
const DEFAULT_SCALE = 5000;
var defaultDevicesConfig = null;

state = {
    scaleValue: null,
    initialScaleValue: ''
}

componentDidMount() {
    this.ensureDefaultDevicesConfig(this.props);
}

ensureDefaultDevicesConfig(props) {
    if(!defaultDevicesConfig && props.appInterface && props.appInterface.constants) {
        defaultDevicesConfig = {
            viewPorts: props.appInterface.constants.MapControlTypes.VIEWPORTS,
            devices: props.appInterface.constants.MapControlTypes.Devices
        };
    }
}

onScaleFilterChange(value, blur, initialValue) {
	const scale = parseInt(value.replace(/\s/g,''));

	if (!isNaN(scale)) {
		this.setState({
			scaleValue: Math.round(scale),
			initialScaleValue: initialValue ? DEFAULT_SCALE.toString() : this.state.initialScaleValue
		});
	}
}

onScaleFilterOptionSelect(value) {
	this.scaleSearchSelect.setState({
		...this.scaleSearchSelect.state,
		filter: value
	});
	this.onScaleFilterChange(value, true);
}

onBlurScaleFilter() {
	if (this.scaleSearchSelect && this.scaleSearchSelect.decoratedComponentInstance && this.scaleSearchSelect.decoratedComponentInstance.state.filter === '') {
		this.scaleSearchSelect.decoratedComponentInstance.setState({filter: this.state.scaleValue});
	}
}

render() {
    let localization = this.props.appInterface.tree.select('workspace', 'localization').get();
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <SearchSelect devicesConfig={defaultDevicesConfig}
					ref={(searchSelect) => this.scaleSearchSelect = searchSelect}
					localization={localization}
					className="searchFieldSelect selectable scale"
					placeholder={localization.reports.scaleLineName}
					showAllOptions={false}
					hideSearchButton={true}
					typeOfOptions='number'
					onFilterChange={this.onScaleFilterChange.bind(this)}
					onChange={this.onScaleFilterOptionSelect.bind(this)}
					onBlurFilter={this.onBlurScaleFilter.bind(this)}
					isWithoutArrowDown={true}
					initialFilterValue={parseInt(this.state.initialScaleValue).toLocaleString('cs-CZ')}
					options={options}
					disableTextHighlighting={true}
				/>
            </div>
        </div>
    )
}
```
[Back](#components)

#### SelectField
**Description**: Inner component for [searchFieldSelect](#searchfieldselect) not designated for separate usage.

**Props**:
```
SelectField.propTypes = {
  name: PropTypes.string,
  options: PropTypes.array,
  disabled: PropTypes.bool
}
```
`+` [Field](#field) `props`

`As a component designated for` [searchFieldSelect](#searchfieldselect) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### SelectorCell
**Description**: Special cell in a [DataGrid](#datagrid) for selecting rows.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/selectorcell1.png?raw=true)

**Props**:
```
SelectorCell.propTypes = {
  data: PropTypes.array,
  selectionSettings: PropTypes.object,
  rowIndex: PropTypes.number,
  highlightedRow: PropTypes.number,
  customRowHighlighted: PropTypes.object,
  customRowClassName: PropTypes.string
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### SelectorHeaderCell
**Description**: Special header cell in a [DataGrid](#datagrid) for selecting rows.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/selectorheadercell1.png?raw=true)

**Props**:
```
SelectorHeaderCell.propTypes = {
  checked: PropTypes.bool,
  toolTip: PropTypes.string,
  onChange: PropTypes.func,
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### Spinner
**Description**: An indicator, that the application is working.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/spinner1.png?raw=true)

**Props**:
```
Spinner.propTypes = {
  width: PropTypes.number,
  height: PropTypes.number,
  enabled: PropTypes.bool,
  color: PropTypes.string
}
```
- **width** - width in pixels, default value is 20
- **height** - height in pixels, default value is 20
- **enabled** - if false, the spinner becomes hidden
- **color** - background color for the spinning icon, for example 'red' or '#FF0000'

**Usage**:
``` javascript
render() {
    return (
        <div>
            {
                this.state.working && <div className="ark-Malfunction ModalDialog-disabler-content">
                    <Spinner/>
                </div>
            }
        </div>
    )
}
```
[Back](#components)

#### TextCell
**Description**: One cell in a [DataGrid](#datagrid) suited for String values.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/textcell1.png?raw=true)

**Props**:
```
TextCell.propTypes = {
  value: PropTypes.any
}
```

**Usage**:
`As a component designated for` [DataGrid](#datagrid) `complex type, it is not described in further detail. In case of need, reach out for Arkance Czech development team for help.`

[Back](#components)

#### TextField
**Description**: Text input that does not allow multiple lines.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/textfield1.png?raw=true)

**Props**:
```
TextField.propTypes = {
  name: PropTypes.string,
  maxLength: PropTypes.number,
  disabled: PropTypes.bool,
  readOnly: PropTypes.bool
```
`+` [Field](#field) `props`
- **name** - name attribute of the html textarea
- **maxLength** - maximum number of characters allowed in the input
- **disabled** - if true, input is visible, but grayed-out and unable to be changed
- **readOnly** - if true, input is visible, but unable to be changed

**Usage**:
``` javascript
handleDescriptionChanged(args) {
    this.setState({
        description: args
    });
}

render() {
    let localizationValidator = this.props.appInterface.tree.select('workspace', 'localization', 'validator').get();
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <TextField label={"Description"} 
                    disabled={false} 
                    value={this.state.description} 
                    verticalCompactnessLevel={2} 
                    isBordered={false} 
                    localization={localizationValidator} 
                    required={true} 
                    onChange={this.handleDescriptionChanged.bind(this)}/>
            </div>
        </div>
    )
}
```
[Back](#components)

#### TextHighlight
**Description**: Text with a colored indication of some important subtext.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/texthighlight1.png?raw=true)

**Props**:
```
TextHighlight.propTypes = {
  phrase: PropTypes.any,
  caseSensitive: PropTypes.bool,
  children: PropTypes.any,
  bold: PropTypes.bool
}
```
- **phrase** - the important subtext of the main text
- **caseSensitive** - if the colored indication of the *phrase* inside the *text* should be case sensitive. Default value is false
- **children** - representation of the main text, either in form of a string or any html element
- **bold** - if true, every text in ObjectCard is bold. Default value is false

**Usage**:
``` javascript
render() {
    let wholeText = 'Hello everyone';
    let highlightedText = 'ell';
    return (
        <div>
            <TextHighlight phrase={highlightedText}>{wholeText}</TextHighlight>
        </div>
    )
}
```
[Back](#components)

#### ToolsButton
**Description**: Button filling the whole width having border and text color defined by theme.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/toolsbutton1.png?raw=true)

**Props**:
```
ToolsButton.propTypes = {
  disabled: PropTypes.bool,
  onClick: PropTypes.func,
  text: PropTypes.string,
  toolTip: PropTypes.string,
  icon: PropTypes.string,
  style: PropTypes.object,
  iconStyle: PropTypes.object
}
```
- **disabled** - if true, button is visible, but grayed-out and unable to be clicked
- **onClick**
- **text**
- **toolTip**
- **icon** - voluntary icon type - for available types see [Icon](#icon)
- **style** - additional style object to be applied to the "button" element
- **iconStyle** - additional style object to be applied to the "Icon" element

**Usage**:
``` javascript
render() {
    return (
        <ToolsButton 
            disabled={false}
            onClick={this.handleToggleButtonClick.bind(this)}
            icon="filter"
            style={this.state.toggleActive ? {color: 'rgb(253, 86, 86)', borderColor: 'rgb(253, 86, 86)'} : null}
            text={this.state.toggleActive ? "Turn on" : "Turn off"}
        />
    )
}
```
[Back](#components)

#### TriconField
**Description**: Text field extended by one or more icons on the right side.

![](https://cdn.jsdelivr.net/npm/uni-module-common/img/triconfield1.png?raw=true)

**Props**:
```
TriconField.propTypes = {
  disabled: PropTypes.bool,
  readOnly: PropTypes.bool,
  emptyPlaceholder: PropTypes.string,
  icon1Type: PropTypes.string,
  icon1ToolTip: PropTypes.string,
  icon1ClassName: PropTypes.string,
  disableHover1: PropTypes.bool,
  onIcon1Click: PropTypes.func,
  icon2Type: PropTypes.string,
  icon2ToolTip: PropTypes.string,
  icon2ClassName: PropTypes.string,
  disableHover2: PropTypes.bool,
  onIcon2Click: PropTypes.func,
  icon3Type: PropTypes.string,
  icon3ToolTip: PropTypes.string,
  icon3ClassName: PropTypes.string,
  disableHover3: PropTypes.bool,
  onIcon3Click: PropTypes.func
}
```
`+` [Field](#field) `props`
- **disabled** - if true, input is visible, but grayed-out and unable to be changed
- **readOnly** - if true, input is visible, but unable to be changed
- **emptyPlaceholder** - text to be shown if the field is empty (*empty* prop is true)
- **icon1Type** - type of the first icon, see [Icon](#icon) for possible choices
- **icon1ToolTip** - tooltip of the first icon
- **icon1ClassName** - additional class that appends the default "Icon" class of the first icon
- **disableHover1** - if true, the default icon hover effect of the first icon is turned off
- **onIcon1Click**
- **icon2Type** - type of the second icon, see [Icon](#icon) for possible choices
- **icon2ToolTip** - tooltip of the second icon
- **icon2ClassName** - additional class that appends the default "Icon" class of the second icon
- **disableHover2** - if true, the default icon hover effect of the second icon is turned off
- **onIcon2Click**
- **icon3Type** - type of the third icon, see [Icon](#icon) for possible choices
- **icon3ToolTip** - tooltip of the third icon
- **icon3ClassName** - additional class that appends the default "Icon" class of the third icon
- **disableHover3** - if true, the default icon hover effect of the third icon is turned off
- **onIcon3Click**

**Usage**:
``` javascript
validateSolver() {
    if(!this.state.ticketSolver) {
        throw new Error("Insufficient data");
    }
}

handleSolverIcon1Click() {
    // TODO
}

render() {
    return (
        <div>
            <div className="Panel-toolbar">
                ...
            </div>
            <div className="Panel-content">
                <TriconField label={"Ticket solver"}
                    empty={false}
                    value={this.state.ticketSolver ? this.state.ticketSolver.value : "Not filled"}
                    validate={this.validateSolver.bind(this)}
                    icon1Type="arrowRight"
                    icon1ToolTip={"Choose"}
                    onIcon1Click={this.handleSolverIcon1Click.bind(this)}
                    icon2Type={this.state.ticketSolver ? "check" : "info"}
                    icon2ClassName={this.state.ticketSolver ? "green" : "red"}
                    disableHover2={true}
                />
            </div>
        </div>
    )
}
```
[Back](#components)

## Contributing
No contributions are allowed.

## License
Commercial usage allowed only with the permission of ARKANCE company https://arkance.world