# Inspector control

Inspector is a visual configuration tool that is used in several places of Winter back-end. The most known usage of Inspector is the CMS components configuration feature, but Inspector is not limited with the CMS. In fact, it's a universal tool that can be used with any element on a back-end page.

The Inspector loads the configuration schema from an inspectable element, builds the user interface, and writes values entered by users back to the inspectable element. The first version of Inspector was supporting only a few scalar value types - strings and Booleans, without an option to edit any complex data.

The current version of Inspector allows to edit any imaginable data structures, including cases where users create enumerable data elements right in the Inspector interface.

This section describes the client-side Inspector API without going into details about the back-end usage of the data Inspector generates. Inspector accepts the configuration schema in JSON format and generates values in JSON format as well. Providing the configuration and interpreting the generated values is up to developers. For example, the CMS module uses information returned from component's defineProperties() method to generate the configuration JSON string and converts JSON values generated by Inspector to the components configuration in CMS templates. In this document we are focusing only on the JSON format.

## Configuring inspectable elements

Clicking an inspectable element displays Inspector for that element. Any HTML element could be made inspectable by adding data attributes to it. The required attributes are:

* `data-inspectable` - indicates that Inspector should be created when the element is clicked.
* `data-inspector-title` - sets the Inspector popup title.
* `data-inspector-config` - contains the Inspector configuration JSON string. If this attribute is not specified, the configuration is loaded from the server, see the [Dynamic configuration and dynamic items](#dynamic-configuration-and-dynamic-items) section below.

Inspectable elements should also contain a hidden input element used by Inspector for reading and writing values. The input element should be marked with the `data-inspector-values` data attribute.

Example inspectable element markup:

```html
<div
    data-inspectable
    data-inspector-title="Some inspectable element"
    data-inspector-description="Some description">
        <input
            data-inspector-values
            type="hidden"
            value="JSON"/>
</div>
```

### Optional data attributes

There are several optional data attributes and features that could be defined in an inspectable element or in elements around it:

* `data-inspector-offset` - sets offset, in pixels, for the Inspector popup.
* `data-inspector-offset-x` - sets horizontal offset, in pixels, for the Inspector popup.
* `data-inspector-offset-y` - sets vertical offset, in pixels, for the Inspector popup.
* `data-inspector-placement` - sets defines placement for the Inspector popup, optional. If omitted, Inspector evaluates a placement automatically. Supported values: top, bottom, left, top.
* `data-inspector-fallback-placement` - sets less preferable placement for the Inspector popup, optional. This value is used if Inspector can't use the placement specified in data-inspector-placement. Supported values: top, bottom, left, top.
* `data-inspector-external-parameters` - if this attribute exists in any parent element of the inspectable element, the external parameters editors will be enabled in Inspector (unless property-specific rules cancel the external editor).

### Dynamic configuration and dynamic items

In case if the `data-inspector-config` attribute is missing in the inspectable element Inspector tries to load its configuration from the server. An important note - there should be a FORM element wrapping inspectable elements in order to use any dynamic features of Inspector.

The AJAX request used for loading the configuration from the server is named `onGetInspectorConfiguration`. The handler should be defined in the back-end controller and should return an array containing the Inspector configuration (in the PHP equivalent of the JSON configuration structure described later in this section), inspector title and description. Example of a server-side AJAX dynamic configuration request handler:

```php
public function onGetInspectorConfiguration()
{
    // Load and use some values from the posted form
    //
    $someValue = Request::input('someValue');

    ... do some processing ...

    return [
        'configuration' => [
            'properties'  => [list of properties],
            'title'       => 'Inspector title',
            'description' => 'Inspector description'
        ]
    ];
}
```

Some Inspector editors - (drop-down, set, autocomplete) support static and dynamic options. Dynamic options are requested from the server, rather than being defined in the configuration JSON string. For using this feature, the inspectable element must have the `data-inspector-class` attribute defined. The attribute value should contain a name of a PHP class corresponding to the inspectable element.

The server-side controller should use the `Backend\Traits\InspectableContainer` trait in order to provide the dynamic options loading. The inspectable PHP class (specified with `data-inspector-class`) must either have a method `get[Property]Options()`, where the [Property] part corresponds the name of the dynamic property, or `getPropertyOptions($propertyName)` method that is more universal and accepts the property name as a parameter. The methods should return the `options` array containing associative arrays with keys `option` and `value`. Example:

```php
public function getContextOptions()
{
    $optionsArray = [];

    $optionsArray[] = ['value' => 'create', 'title' => 'Create'];
    $optionsArray[] = ['value' => 'update', 'title' => 'Update'];
    $optionsArray[] = ['value' => 'delete', 'title' => 'Delete'];

    return [
        'options' => $optionsArray
    ];
}
```

### Container and popups

By default Inspector is displayed in a popup, but there's an option to display it right on the page, in a container element. To enable this option, all inspectable elements should be wrapped into another element with `data-inspector-container` attribute. The attribute value should be a CSS selector pointing to an element inside the wrapper. Example:

```html
<div data-inspector-container=".inspector-container">
   <div class="inspector-container"></div>
   <div data-inspectable ... ...>
   <div data-inspectable ... ...>
</div>
```

The inner element will act as host element for Inspector when an inspectable element is clicked. The element should have the `inspector-container` class and can be optionally marked with `data-inspector-scrollable` attribute to make the Inspector scrollable. For the scrolling feature, the container element should have height defined explicitly.

When the container is used, Inspector is still displayed in a popup by default, but users can click an icon in the Inspector header to move it to the container.

## Data schema configuration

Inspector configuration, defined with `data-inspector-config` attribute or loaded from the server, should be an array containing a list of property definition. All examples in this section use JSON format. Below is an example of a configuration for two properties:

```json
[
    {
        "property": "firstName",
        "title": "First name",
        "type": "string"
    },
    {
        "property": "lastName",
        "title": "Last name",
        "type": "string"
    }
]
```

This configuration creates two text fields with titles "First name" and "Last name". When the data is saved back to the inspectable element (to the `data-inspector-values` hidden input element), it would have the following format:

```json
{"firstName":"John", "lastName":"Smith"}
```

Each property should have attributes `property`, `title` and `type`. The `type` attribute defines a type of an editor that should be created for the property. The supported editors are described further.

Other attributes supported by all (or most of the) property types are:

* `description` - description string, which is available in a tooltip displayed when a user overs the 'i' icon in the property editor.
* `group` - allows to group multiple properties. The attribute should contain a group name. Groups could be collapsed by users, making the Inspector interface less cluttered.
* `showExternalParam` - enables the inspector parameter editor for the property. External parameters are currently used only by the CMS. Note that some property types do not support external property editors. See also `data-inspector-external-parameters` attribute described above.
* `placeholder` - text to display in the editor if property value is empty.
* `validation` - validation configuration. See the complete validation description below.
* `default` - default property value. The property value format depends on the property type - for the `string` type it's an array, for `stringList` type it's an array of strings. See more details below.

All other configuration properties are specific for different property types.

### String editor

String editor allows entering a single line of a text and represented with a simple input text field. The editor doesn't have any specific parameters. The optional `default` parameter for the editor should contain a string.

```json
{
    "property": "firstName",
    "title": "First name",
    "type": "string",
    "default": "John"
}
```

The editor generates string values:

```json
{"firstName":"Sam"}
```

### Text editor

Text editor allows entering multi-line long text values in a popup window. The editor doesn't have any specific parameters. The optional `default` parameter for the editor should contain a string.

```json
{
    "property": "description",
    "title": "Description",
    "type": "text",
    "default": "This is a default description"
}
```

The editor generates string values:

```json
{"description":"This is a description"}
```

### String list editor

Allows users to enter lists of strings. The editor opens in a popup window and displays a text area. Each line of text represents an element in the result array. The optional `default` parameter should contain an array of strings. Example:

```json
{
    "property": "items",
    "title": "Items"
    "type": "stringList",
    "default": ["String 1", "String 2"]
}
```

A value generated by the editor is an array of strings, for example:

```json
{"items":["String 1","String 2","String 3"]}
```

### Autocomplete editor

This editor works like the `string` editor, but includes the autocomplete feature. Autocompletion options can be specified statically, with the `items` parameter or loaded dynamically. Example with static options:

```json
{
    "property": "condition",
    "title": "Condition"
    "type": "autocomplete",
    "items": {"start": "Start", "end": "End"}
}
```

The items are specified as a key-value object. The `items` parameter is optional, if it's not provided, the items will be loaded from the server - see [Dynamic configuration and dynamic items](#dynamic-configuration-and-dynamic-items) section above.

Values generated by the editor are strings. Example:

```json
{"condition":"start"}
```

Fields of this type do not support external property editors.

### Checkbox editor

Properties of this type are represented with a checkbox in the Inspector UI. This property doesn't have any special parameters. The `default` parameter, if specified, should contain a Boolean value or string values "true", "false", "1", "0". Example:

```json
{
    "property": "enabled",
    "title": "Enabled",
    "type": "checkbox",
    "default": true
}
```

Values generated by the editor are 0 (unchecked) or 1 (checked). Example:

```json
{"enabled":1}
```

### Dropdown editor

Displays a drop-down list. Options for the drop-down list can be specified statically with the `options` attribute or loaded from the server dynamically. Example:

```json
{
    "property": "action",
    "title": "Action",
    "type": "dropdown",
    "options": {
        "show": "Show",
        "hide": "Hide",
        "enable": "Enable",
        "disable": "Disable",
        "empty": "Empty"
    }
}
```

The `options` attribute should be a key-value object. If the attribute is not specified, Inspector will try to load options from the server - see [Dynamic configuration and dynamic items](#dynamic-configuration-and-dynamic-items) section above.

The editor generates a string value corresponding to the selected option, for example:

```json
{"action":"hide"}
```

### Dictionary editor

Dictionary editor allows to create key-value pairs with a simple user interface consisting of a table with two columns. The `default` parameter, if specified, should contain a key-value object. Example:

```json
{
    "property": "options",
    "title": "Options",
    "type": "dictionary",
    "default": {"option1": "Option 1"}
}
```

The editor generates an object value, for example:

```json
{"options":{"option1":"Option 1","option2":"Option 2"}}
```

The dictionary editor supports validation for the entire set (`required` and `length` validators) and for keys and values separately. See the [validation description](#defining-the-validation-rules) further in this document. The `validationKey` and `validationValue` define validation for keys and values, for example:

```json
{
    "property": "options",
    "title": "Options",
    "type": "dictionary",
    "validation": {
        "required": {
            "message": "Please create options"
        },
        "length": {
            "min": {
                "value": 2,
                "message": "Create at least two options."
            }
        }
    },
    "validationKey": {
        "regex": {
            "pattern": "^[a-z]+$",
            "message": "Keys can contain only lowercase Latin letters"
        }
    },
    "validationValue": {
        "regex": {
            "pattern": "^[a-zA-Z0-9]+$",
            "message": "Values can contain only Latin letters and digits"
        }
    }
}
```

### Object editor

Allows to define an object with specific properties editable by users. Object properties are specified with the `properties` attribute. The value of the attribute is an array, which has exactly the same structure as the Inspector properties array.

```json
{
    "property": "address",
    "title": "Address",
    "type": "object",
    "properties": [
        {
            "property": "streetAddress",
            "title": "Street address",
            "type": "string"
        },
        {
            "property": "city",
            "title": "City",
            "type": "string"
        },
        {
            "property": "country",
            "title": "Country",
            "type": "dropdown",
            "options": {"us": "US", "ca": "Canada"}
        }
    ]
}
```

The example above creates an object with three properties. Two of them are displayed as text fields, and the third as a drop-down.

Object editor values are objects. Example:

```json
{
    "address": {
        "streetAddress":"321-210 Second ave",
        "city":"Springfield",
        "country":"us"
    }
}
```

The object properties can be of any type supported by Inspector, including other objects.

There's a way to exclude an object from Inspector values completely, if one of the object fields is empty. The field is identified with `ignoreIfPropertyEmpty` parameter. For example:

```json
{
    "property": "address",
    "title": "Address",
    "type": "object",
    "ignoreIfPropertyEmpty": "title",
    "properties": [
        {
            "property": "streetAddress",
            "title": "Street address",
            "type": "string"
        },
        {
            "property": "city",
            "title": "City",
            "type": "string"
        }
    ]
}
```

In the example above, if the street address is not specified, the object ("address") will be completely removed from the Inspector output. If there are any validation rules defined on other object properties and the required property is empty, those rules will be ignored.

A `default` value for the editor, if specified, should be an object with the same properties as defined in the `properties` configuration parameter.

Object editors do not support the external property editor feature.

### Object list editor

The object list editor allows users to create multiple objects with a pre-defined structure. For example, it could be used for creating a list of person, where each person has a name and address.

The properties of objects that can be created with the editor are defined with `itemProperties` parameter. The parameter should contain an array of properties, similar to Inspector configuration array. Another required parameter is `titleProperty`, which identifies a property that should be used as a title in Inspector UI. Example configuration:

```json
{
    "property": "people",
    "title": "People",
    "type": "objectList",
    "titleProperty": "fullName",
    "itemProperties": [
        {
            "property": "fullName",
            "title": "Full name",
            "type": "string"
        },
        {
            "property": "address",
            "title": "Address",
            "type": "string"
        }
    ]
}
```

The array of properties defined with `itemProperties` supports all property types.

The Object List editor type doesn't support default values.

By default the value created by the editor of this type is a non-associative array:

```json
{
    "people":[
        {"fullName":"John Smith","address":"Palo Alto"},
        {"fullName":"Bart Simpson","address":"Springfield"}
     ]
}
```

If the result value should be an associative array (object), use the `keyProperty` configuration option. The option value should refer to a property that should be used as a key. The key property can use only the string or drop-down editors, its value should be unique and cannot be empty. Example:

```json
{
    "property": "people",
    "title": "People",
    "type": "objectList",
    "titleProperty": "fullName",
    "keyProperty": "login",
    "itemProperties": [
        {
            "property": "fullName",
            "title": "Full name",
            "type": "string"
        },
        {
            "property": "login",
            "title": "Login",
            "type": "string"
        },
        {
            "property": "address",
            "title": "Address",
            "type": "string"
        }
    ]
}
```

The `login` property in the example above will be used as a key in the result value:

```json
{
    "people":{
        "john":{"fullName":"John Smith","address":"Palo Alto"},
        "bart":{"fullName":"Bart Simpson","address":"Springfield"}
    }
}
```

### Set editor

The set editor allows users to select multiple predefined options with checkboxes. Set items can be specified statically with the configuration, using the `items` parameter, or loaded dynamically. Example with static items definition:

```json
{
    "property": "context",
    "title": "Context",
    "type": "set",
    "items": {
        "create": "Create",
        "update": "Update",
        "preview": "Preview"
    },
    "default": ["create", "update"]
}
```

The `items` attribute should be a key-value object. If the attribute is not specified, Inspector will try to load options from the server - see [Dynamic configuration and dynamic items](#dynamic-configuration-and-dynamic-items) section above.

The `default` parameter, if specified, should be an array listing item keys selected by default.

Set editors do not support the external property editor feature.

## Defining the validation rules

Inspector support several validation rules that can be applied to properties. Validation rules can be applied to top-level properties as well as to internal property definitions of object and object list editors. There are two ways to define validation rules - the legacy syntax and the new syntax.

The legacy syntax is supported for the backwards compatibility with existing CMS components definitions. This syntax will always be supported, but it's limited, and cannot be mixed with the new syntax. Example of the legacy syntax:

```json
{
    "property": "name",
    "title": "Name",
    "type": "string",
    "required": true,
    "validationPattern": "^[a-zA-Z]+$"
    "validationMessage": "The Name field is required and can contain only Latin letters.",
}
```

The legacy syntax supports only two validation rules - required and regular expression. The new syntax is much more flexible and extendable:

```json
{
    "property": "name",
    "title": "Name",
    "type": "string",
    "validation": {
        "required": {
            "message": "The Name field is required"
        },
        "regex": {
            "message": "The Name field can contain only Latin letters.",
            "pattern": "^[a-zA-Z]+$"
        }
    }
}
```

The key value in the `validation` object refers to a validator (see below). Validators are configured with objects, which properties depend on a validator. One property - `message` is common for all validators.

### required validator

Checks if a value is not empty. The validator can be used with any editor, including complex editors (sets, dictionaries, object lists, etc.). Example:

```json
{
    "property": "name",
    "title": "Name",
    "type": "string",
    "validation": {
        "required": {
            "message": "The Name field is required"
        }
    }
}
```

### regex validator

Validates string values with a regular expression. The validator can  be use only with string-typed editors. Example:

```json
{
    "property": "name",
    "title": "Name",
    "type": "string",
    "validation": {
        "regex": {
            "message": "The Name field can contain only Latin letters",
            "pattern": "^[a-z]+$",
            "modifiers": "i"
        }
    }
}
```

The regular expression is specified with the required `pattern` parameter. The `modifiers` parameter is optional and can be used for setting regular expression modifiers.

### integer validator

Checks if the value is integer and can optionally validate if the value is within a specific interval. The validator can be used only with string-typed editors. Example:

```json
{
    "property": "numOfColumns",
    "title": "Number of Columns",
    "type": "string",
    "validation": {
        "integer": {
            "message": "The Number of Columns field should contain an integer value",
            "allowNegative": true,
            "min": {
                "value": -10,
                "message": "The number of columns should not be less than -10."
            },
            "max": {
                "value": 10,
                "message": "The number of columns should not be greater than 10."
            }
        }
    }
}
```

Supported parameters:

* `allowNegative` - optional, determines if negative values are allowed. By default negative values are not allowed.
* `min` - optional object, defines the minimum allowed value and error message. Object fields:
    * `value` - defines the minimum value.
    * `message` - optional, defines the error message.
* `max` - optional object, defines the maximum allowed value and error message. Object fields:
    * `value` - defines the maximum value.
    * `message` - optional, defines the error message.

### float validator

Checks if the value is a floating point number. The parameters for this validator match the parameters of the **integer** validator described above. Example:

```json
{
    "property": "amount",
    "title": "Amount",
    "type": "string",
    "validation": {
        "float": {
            "message": "The Amount field should contain a positive floating point value."
        }
    }
}
```

Valid floating point number formats:

* 10
* 10.302
* -10 (if `allowNegative` is `true`)
* -10.84 (if `allowNegative` is `true`)

### length validator

Checks if a string, array or object is not shorter or longer than specified values. This validator can work with the string, text, set, string list, dictionary and object list editors. In multiple-value editors (set, string list, dictionary and object list) it validates the number of items created in the editor.

> **Note**: the `length` validator doesn't validate empty values. For example, if it's applied to a set editor, and the set is empty, the validation will pass regardless of the `min` and `max` parameter values. Use the `required` validator together with the `length` validator to make sure that the value is not empty before the length validation is applied.

```json
{
    "property": "name",
    "title": "Name",
    "type": "string",
    "validation": {
        "length": {
            "min": {
                "value": 2,
                "message": "The name should not be shorter than two letters."
            },
            "max": {
                "value": 10,
                "message": "name should not be longer than 10 letters."
            }
        }
    }
}
```

Supported parameters:

* `min` - optional object, defines the minimum allowed length and error message. Object fields:
    * `value` - defines the minimum value.
    * `message` - optional, defines the error message.
* `max` - optional object, defines the maximum allowed length and error message. Object fields:
    * `value` - defines the maximum value.
    * `message` - optional, defines the error message.

## Inspector events

Inspector triggers several events on the inspectable elements.

### change

The `change` event is triggered after Inspector applies updated values to the inspectable element. The event is triggered only if the user has changed values in the Inspector UI.

### showing.oc.inspector

The `showing.oc.inspector` event is triggered before Inspector is displayed. The event handler can optionally stop the process with calling `ev.isDefaultPrevented()`. Example - prevent Inspector showing:

```js
$(document).on('showing.oc.inspector', 'div[data-inspectable]', function(ev, data){
    ev.preventDefault()
})
```

The handler could  perform any required processing, even asynchronous, and then call the callback function passed to the handler, to continue showing the Inspector. In this case the handler should call `ev.stopPropagation()` method to stop the default Inspector initialization. Example - continue showing after some processing:

```js
$(document).on('showing.oc.inspector', 'div[data-inspectable]', function(ev, data){
    ev.stopPropagation()
    // The callback function can be called asynchronously
    data.callback()
})
```

### hiding.oc.inspector

The `hiding.oc.inspector` is called before Inspector hiding process starts. The handler can stop the hiding with calling `ev.preventDefault()`. Example:

```js
$(document).on('hiding.oc.inspector', 'div[data-inspectable]', function(ev, data){
    if (!confirm('Allow hiding?')) {
        ev.preventDefault()
    }
})
```

The values entered in Inspector are available through the `values` element of the second handler argument:

```js
$(document).on('hiding.oc.inspector', 'div[data-inspectable]', function(ev, data){
   console.log(data.values)
})
```

### hidden.oc.inspector

The `hidden.oc.inspector` is triggered after Inspector is hidden.