🎉 JointJS has new documentation! 🥳
Inspector plugin creates an editor and viewer of cell model properties. It creates a two-way data-binding between the cell model and a generated HTML form with input fields. These input fields can be defined in a declarative fashion using a plain JavaScript object. Not only visual attributes can be configured for editing in the inspector but also custom data that will be set on the cell model! This all makes the inspector an extremely flexible and useful widget for use in applications.
Include joint.ui.inspector.css
and joint.ui.inspector.js
files to your HTML:
<link rel="stylesheet" type="text/css" href="joint.ui.inspector.css"/>
<script src="joint.ui.inspector.js"></script>
You can create the Inspector instance as follows:
const inspector = new joint.ui.Inspector(options);
inspector.render();
document.body.appendChild(inspector.el);
Usually the applications display only one inspector at the time, which is used for various application models and differs in the configuration of input fields only. For this common use-case we've introduced a static helper - joint.ui.Inspector.create()
. It makes sure the previous instance (if there is any) is properly removed, it creates a new one and renders it into the DOM. It also keeps track of open/closed groups and restores them based on the last used state. It can be used as follows:
joint.ui.Inspector.create('#inspector', options);
For more information about the create
method visit the Inspector API chapter.
paper.on('element:pointerdown', function(elementView) {
// open the inspector when the user interacts with an element
joint.ui.Inspector.create('#inspector', {
cell: elementView.model,
inputs: {
attrs: {
body: {
fill: {
type: 'color-palette',
options: [
{ content: '#FFFFFF' },
{ content: '#FF0000' },
{ content: '#00FF00' },
{ content: '#0000FF' },
{ content: '#000000' }
],
label: 'Fill color',
group: 'presentation',
index: 1
},
stroke: {
type: 'color-palette',
options: [
{ content: '#FFFFFF' },
{ content: '#FF0000' },
{ content: '#00FF00' },
{ content: '#0000FF' },
{ content: '#000000' }
],
label: 'Outline color',
group: 'presentation',
index: 2
},
strokeWidth: {
type: 'range',
min: 0,
max: 50,
unit: 'px',
label: 'Outline thickness',
group: 'presentation',
index: 3
}
},
label: {
text: {
type: 'textarea',
label: 'Text',
group: 'text',
index: 1
},
fontSize: {
type: 'range',
min: 5,
max: 30,
label: 'Font size',
group: 'text',
index: 2
},
fontFamily: {
type: 'select',
options: ['Arial', 'Times New Roman', 'Courier New'],
label: 'Font family',
group: 'text',
index: 3
}
}
}
},
groups: {
presentation: {
label: 'Presentation',
index: 1
},
text: {
label: 'Text',
index: 2
}
}
});
});
There are two ways to create an instance of the inspector. The first option is to use the joint.ui.Inspector.create(container, options)
static function (where container
is a CSS selector of an HTML element on your page). The second option is to directly create an instance with new joint.ui.Inspector(options)
. The inspector can be configured by the options
object with the following properties:
cellView | joint.dia.CellView | (Mandatory - alternative 1) An ElementView or LinkView which you want to inspect. (Mutually exclusive with cell option.) |
cell | mvc.Model | (Mandatory - alternative 2) An arbitrary mvc model which you want to inspect (i.e. an Element, Link or Graph). (Mutually exclusive with the cellView option.) |
inputs | object | An object that mimics the structure of a cell model. Instead of the final values, it contains definitions of input fields. (The input field is in charge of setting the user input on the specified cell model.) See below for further explanation. |
groups | object | An object that contains group identifiers as keys and group options as values. Each group may contain any number of input fields in the inspector panel. The user can show/hide the whole group by clicking a toggle button. See below for further explanation. |
live | boolean | Should the Inspector update the cell properties immediately (in reaction to an If you need to prevent the Inspector from updating cell properties immediately when the user leaves their corresponding input field (for example, if you need to update cell properties only in reaction to the user pressing an Update button somewhere in your application), set this option to |
multiOpenGroups | boolean | Is the user allowed to have multiple groups opened in the Inspector at the same time? The default is For the classical |
container | string | Element | A CSS selector or DOM element that is the container element which the select-box or 'color-palette' options is appended to. |
validateInput(element, path, type, inspector) | function | A callback function, called by Inspector fields to check whether user input was valid. The function should return The callback function is passed four arguments:
validity property of element :
|
renderFieldContent(options, path, value, inspector) | function | A callback function that returns an HTML or DOM element that will be appended by the Inspector into the space reserved for the field. In other words, this function allows you to define custom fields. The function is passed four arguments:
undefined in some cases, the Inspector will try to understand the field as if it were one of the built-in field types.
|
getFieldValue(attribute, type, inspector) | function | A callback function that returns an object of the form The function is passed three arguments:
|
renderLabel(options, path, inspector) | function | A callback function that returns an HTML or DOM element that will be appended by the Inspector into the space reserved for field label. In other words, this function allows you to define custom labels. The function is passed three arguments:
|
focusField(options, path, element, inspector) | function | A callback function called by Inspector when trying to focus an element. This function allows you to define the focus behaviour for custom fields. The function is passed three arguments:
|
stateKey(model) | function | A callback function that should return a unique identifier for saving/restoring the state of the groups (i.e. which groups are opened and which ones are closed). The default function returns the An alternative method, |
storeGroupsState | boolean | (Applicable only when used with the create method) Should group state be saved? (That is, should the Inspector remember which groups were opened and which groups were closed, when it is reopened?) The default is true . (Group state can be restored by the restoreGroupsState option when the Inspector is created. It defaults to true . See below.) |
restoreGroupsState | boolean | (Applicable only when used with the create method) Should previous group state be restored (if any group state had been saved)? The default is true . |
updateCellOnClose | boolean | (Applicable only when used with the create method) Should the current inspector values be saved to the cell when a new inspector is about to be created? The default is true . |
Options properties storeGroupsState
/ restoreGroupsState
are applicable only if they are passed into the static create()
method. Otherwise, you can use the API methods storeGroupsState()
and restoreGroupsState()
to manually manipulate group states.
The inputs
object is extremely important. Its structure mimics the structure of properties of the cell model. Instead of the final values, it contains definitions of input fields. (The input field is in charge of setting the user input on the specified cell model.)
The options object of inputs can contain the following parameters:
type | string | (Mandatory) The type of the input field. The supported types are:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
label | string | Label for the form input. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
group | string | Group the input belongs to. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
index | number | Index of the input within its group. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
defaultValue | any | The value that will be used in the input field in case the associated cell property is undefined . |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
valueRegExp | string | A regular expression used to extract (and set) a property value on the cell. Use in combination with the defaultValue option to make sure the Inspector does not try to extract something from an undefined value. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
overwrite | boolean | Should the input value overwrite the current contents of the cell? The default is false , which means that the input attributes are merged with the model's current attributes. Example:
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
options | Specific to several input types (
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
min | number |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
max | number |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
properties | object | An object containing definitions of the properties of an object. Specific to the 'object' type. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
item | object | A definition of a generic item of a list. Specific for the 'list' type. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
attrs | object | An object of the form <selector>: { <attributeName>: <attributeValue>, ... } . This object allows you to set arbitrary HTML attributes on the generated HTML for this input field. Useful if you want to mark certain input fields or store some additional content in them (for example, to display a tooltip). |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
when | object | An object containing conditions that determine whether this input should be shown or hidden based on the values of other inputs. For more information see chapter expressions. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
previewMode | boolean | Should preview modebe enabled on the widget? The default is false . Specific to several input types ('select-box' , 'select-button-group' , 'color-palette' ). |
In preview mode, when the user hovers over one of the items in the widget (e.g. it being a dropdown item in the case of the 'select-box'
type), the Inspector still changes the connected model but triggers the change with the dry
flag set to true
. You can then react to this by using:
myCell.on('change:myProperty', function(cell, change, opt) {
if (opt.dry) {
/* do something when in preview mode */
} else {
sendToDatabase(JSON.stringify(graph));
}
});
This is useful, for example, if your application wants to reflect the values of the hovered items in the diagram but does not want to store the change to the database.
Note that the 'list'
and 'object'
types allow you to create input fields in the Inspector for arbitrary nested structures. If your cell contains an object as a property (cell.set('myobject', { first: 'John', last: 'Good' })
), you can instruct the inspector to use the 'object'
type for myobject
property and then define types for each of the nested properties of that object:
var inspector = new joint.ui.Inspector({
cellView: cellView,
inputs: {
myobject: {
type: 'object',
properties: {
first: { type: 'text' },
last: { type: 'text' }
}
}
},
groups: {}
});
Similarly, if your cell contains a list as a property (cell.set('mylist', [{ first: 'John', last: 'Good' }, { first: 'Jane', last: 'Good' }])
), you can instruct the inspector to use the 'list'
type for mylist
property and then define the type of the list item. Importantly, the 'list'
input type enables users to add and remove list items. In our example, the mylist
item contains a nested object, so we need to define its properties as well:
var inspector = new joint.ui.Inspector({
cellView: cellView,
inputs: {
mylist: {
type: 'list',
item: {
type: 'object',
properties: {
first: { type: 'text' },
last: { type: 'text' }
}
}
}
},
groups: {}
});
You can specify options
parameter of the field as an source object which contains following properties:
dependencies | string[] | An array of dependencies. Each dependency is the name of a cell property. When one of the properties changes, new data is loaded from the source callback. You can use wildcards for dependency path inside lists. To do that, a wildcard ('${index}' ) has to be placed within the path - it will be dynamically substituted for the actual index of the item inside which the dependency is being evaluated. Dependencies array can be ommited and in that case you can use refreshSources() or refreshSource(path) functions to update the options array. |
||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
source | function | A callback which takes object with callback data and returns array of options or a Promise which returns array of options (each option is an object specific for each field). The callback object parameter is following:
|
Here is the example of a field declaration using source object
inputs: {
array: {
type: 'list',
group: 'data',
item: {
type: 'object',
properties: {
value: {
type: 'radio-group',
options: {
dependencies: ['array/${index}/options'],
source: (data) => {
const { path, value, changedPath } = data.dependencies['array/${index}/options'];
const options = value;
if (Array.isArray(options)) {
return options.map(name => {
return { value: name, content: name };
});
}
return [];
}
}
},
options: {
type: 'list',
item: {
type: 'text'
}
}
}
}
}
}
In this example we are defining two inputs inside of an element's array
property. The first one is the value
property with source options parameter. It depends on the sibling options
field which is a list of textboxes. The source function of value
property triggers on any change inside of the options
list. So in the end radio group will be populated with options from the options
field array.
Each group options object in the groups
object can contain the following parameters:
label | string | A label for the group. This label will be displayed as a header of the group section in the accordion-like inspector. |
---|---|---|
index | number | An index of the group relative to other groups. Use this to put the groups in a certain order. |
closed | boolean | If set to true , the group will be closed by default. |
when | object | An object containing conditions that determine if this group should be shown or hidden based on the values of other inputs. For more information see chapter expressions. |
The inspector relies on expressions defined in the when
parameter to switch the visibility of an input field based on the values of other inputs. Whenever an input field's expression is evaluated to false
(meaning the condition is not met
), the input field is hidden. Otherwise, the input field is shown.
When evaluated in a model context, expressions return a boolean (true
/false
) based on the value of specified model attributes. Expressions are defined recursively as follows:
...where:
path | Is a string determining a property of the model (e.g 'attrs/label/text' , 'property' , 'myobject/nestedProperty' , 'mylist/${index}' , 'mylist/${index}/nestedProperty' ) | ||||||||||||||||||||||
value | Is a number, string or an array (e.g 13 , 'jointjs' , [1, 3, 5] ) | ||||||||||||||||||||||
*options | (Optional) Additional options that affect the evaluation of the expression:
| ||||||||||||||||||||||
primitive | Can be one of the following simple operators:
...and returns |
||||||||||||||||||||||
unary_operator | Accepts exactly one expression
|
||||||||||||||||||||||
multiary_operator | Accepts an array of at least one expression
|
Here are a few valid expressions:
{ eq: { 'size/width': 300 }}
{ regex: { 'attrs/label/text' : 'JointJS|Rappid' }}
{ lt: { 'count': 10 }}
{ in: { 'index': [0,2,4] }}
{ not: { eq: { 'day': 'Monday' }}}
{ and: [{ gte: { 'position/x': 100 }}, { lte: { 'position/x': 400 }}]}
Imagine a scenario where you have a 'select'
input field with options 'email'
and 'tel'
. Below this input field, you want to show either a text field or a number input field, based on the selected option. Assuming your cell properties structure is as follows: { contact_option: 'email', contact_email: '', contact_tel: '' }
, your inspector could look like this:
var inspector = new joint.ui.Inspector({
cell: mycell,
inputs: {
contact_option: { type: 'select', options: ['email', 'tel'] },
contact_email: { type: 'text', when: { eq: { 'contact_option': 'email' }}},
contact_tel: { type: 'number', when: { eq: { 'contact_option': 'tel' }}}
}
});
It is also possible to refer to input fields inside nested objects, by using more complicated paths:
var inspector = new joint.ui.Inspector({
cell: mycell,
inputs: {
user_info: {
type: 'object',
properties: {
contact_option: { type: 'select', options: ['email', 'tel'] },
name: { type: 'text'}
}
},
contact_email: { type: 'text', when: { eq: { 'user_info/contact_option': 'email' }}},
contact_tel: { type: 'number', when: { eq: { 'user_info/contact_option': 'tel' }}}
}
});
It does not make sense to reference list items from the outside, but it does make sense to reference sibling input fields within a list item's when
clause. To do that, a wildcard ('${index}'
) has to be placed within the path - it will be dynamically substituted for the actual index of the item inside which the when
clause is being evaluated:
var inspector = new joint.ui.Inspector({
cell: mycell,
inputs: {
user_list: {
type: 'list',
item: {
type: 'object',
properties: {
contact_option: { type: 'select', options: ['email', 'tel'] },
contact_email: { type: 'text', when: { eq: { 'user_list/${index}/contact_option': 'email' }}},
contact_tel: { type: 'number', when: { eq: { 'user_list/${index}/contact_option': 'tel' }}}
}
}
}
}
});
It is also possible to toggle groups with when
expressions.
var inspector = new joint.ui.Inspector({
groups: {
first: { label: 'F' },
second: { label: 'S', when: { eq: { 'attribute2': true }}}
}
});
As you can see above, ui.Inspector
provides a good list of useful built-in primitive operators (eq
, lt
, in
, ...). However, sometimes this is not enough and applications have special requirements for when fields in the inspector should be hidden/displayed based on other information. To meet this requirement while still taking advantage of the inspector configurability through expressions, ui.Inspector
provides a way to define your own custom operators.
First, the custom primitive operator has to be defined inside the operators
array option on the Inspector. Each operator definition is an object of the form: { custom-primitive: function(cell, value, *arguments) }
. The provided callback function should return true
when the operator condition is successful, and false
otherwise. The cell
parameter is the cell associated with the Inspector, value
is the value of the field at the path
specified in the when
clause (see below), and *arguments
is anything that was passed to the operator (see below).
Second, in the when
clause of a definition within the Inspector's inputs
object, an expression with a custom operator has to be defined according to the following format:
The custom primitive expression can be used as an operand in unary and multiary operators, the same as if it was a built-in primitive expression.
For example, let's say you want to show an inspector field only when a value of another input field is longer (has more characters) than the value of a numeric property set on the associated inspector cell:
var inspector = new joint.ui.Inspector({
cell: mycell,
inputs: {
title: { type: 'text' },
description: { type: 'text', when: { longerThan: { 'title': 'titleThreshold' } } },
},
operators: {
longerThan: function(cell, value, prop, valuePath) {
// value === contents of 'title' input field
// prop === 'titleThreshold'
// valuePath === 'title'
return (value ? (value.length > cell.prop(prop)) : false);
}
}
});
The example above displays the description
field only when the content of title
is longer than a numeric threshold which we have stored in a property on the cell model named titleThreshold
. Now whenever the user types within the 'title'
input field in the inspector and the text becomes longer than cell.get('titleThreshold')
, the description
field appears (and vice versa, if the text becomes shorter than titleThreshold
, the description
field gets hidden).
However, the example above has a small problem. If the value of the titleThreshold
property changes on the cell model (e.g. due to some other change somewhere else in the application), that change is not taken into account by the expression. In order to fix this, we have to tell the inspector that there are prop dependencies that could affect the resolution of the expression in the when
clause - we do that by providing a dependencies
list inside the when
clause. Here's the fixed version of the code provided above:
var inspector = new joint.ui.Inspector({
cell: mycell,
inputs: {
title: { type: 'text' },
description: {
type: 'text',
when: {
longerThan: { 'title': 'titleThreshold' },
dependencies: ['titleThreshold']
}
},
},
operators: {
longerThan: function(cell, value, prop, valuePath) {
// value === contents of 'title' input field
// prop === 'titleThreshold'
// valuePath === 'title'
return (value ? (value.length > cell.prop(prop)) : false);
}
}
});
The following example shows how to reflect a custom shape's validation functions inside your Inspector. The inspector definition provides a custom validateInput
method. That method then refers to Shape's custom validateProperty
method, which uses some common regex validators. Notice that this architecture makes the Shape (the model instance) responsible for accepting/rejecting user input data, not the Inspector. As such, this arrangement manages to fulfill one of the goals of the JointJS framework, namely the separation of Model-View-Controller components from each other.
(function(joint, Shape) {
joint.setTheme('modern');
var paper = new joint.dia.Paper({
el: document.getElementById('paper'),
width: 500,
height: 500
});
var shape = new Shape();
shape.position(150,50);
shape.size(200,200);
shape.addTo(paper.model);
var inspector = joint.ui.Inspector.create('#inspector', {
cell: shape,
inputs: shape.getInspectorDefinition(),
validateInput: function(el, path, type, inspector) {
const value = inspector.parse(type, inspector.getFieldValue(el, type), el);
el.classList.remove('error');
Array.from(el.parentNode.querySelectorAll('error') || []).forEach((error) => error.remove());
const error = shape.validateProperty(path, value);
if (error) {
const errorEl = document.createElement('error');
errorEl.textContent = error;
el.classList.add('error');
el.parentNode.insertBefore(errorEl, el);
}
return !error;
}
});
// run the first validity check
inspector.updateCell();
})(joint, joint.dia.Element.define('Shape', {
attrs: {
body: {
refWidth: '100%',
refHeight: '100%',
fill: '#dddddd',
stroke: 'lightblue',
strokeWidth: 2
}
},
phoneNumber: '',
emailAddress: 'org@client.io'
}, {
markup: [{
tagName: 'rect',
selector: 'body'
}],
REGEX_PHONE_NUMBER: /^(\+\d{1,2}\s)?\(?\d{3}\)?[\s.-]\d{3}[\s.-]\d{4}$/,
REGEX_HEXCOLOR: /^#([a-f0-9]{3}){1,2}\b/i,
REGEX_EMAIL_ADDRESS: /^\w+@[a-zA-Z_]+?\.[a-zA-Z]{2,3}$/,
validateProperty: function(path, value) {
switch (path) {
case 'attrs/body/stroke':
case 'attrs/body/fill':
if (this.REGEX_HEXCOLOR.test(value)) break;
return 'Invalid Color (e.g. #ff0000)';
case 'attrs/body/strokeWidth':
if (Number.isFinite(value) && value >= 0) break;
return 'Invalid Stroke Width (A positive number)';
case 'phoneNumber':
if (this.REGEX_PHONE_NUMBER.test(value)) break;
return 'Invalid Phone Number (e.g. 123-456-7890)';
case 'emailAddress':
if (this.REGEX_EMAIL_ADDRESS.test(value)) break;
return 'Invalid Email Address.';
}
return null;
},
getInspectorDefinition: function() {
return {
'attrs/body/fill': {
type: 'text',
label: 'Fill Color'
},
'attrs/body/stroke': {
type: 'text',
label: 'Stroke Color'
},
'attrs/body/strokeWidth': {
type: 'number',
label: 'Stroke Width'
},
'phoneNumber': {
type: 'text',
label: 'Phone Number'
},
'emailAddress': {
type: 'text',
label: 'Email Address'
}
};
}
}));
The Inspector has a useful built-in set of ready-to-use field types. However, in some cases, you might want to render your own custom fields (or to integrate a third party widget) while still taking advantage of the two-way data binding and the configuration options provided by Inspector. This can be done with two Inspector options: renderFieldContent(options, path, value)
and getFieldValue(attribute, type)
.
The following example shows how to render two buttons in a single custom field and how to integrate the Select2 widget for advanced select boxes:
function createInspector(cellView) {
joint.ui.Inspector.create('.inspector-container', {
cellView: cellView,
inputs: {
attrs: {
label: {
style: {
textDecoration: {
label: 'Text Style',
type: 'select2',
group: 'text-decoration',
options: ['none', 'underline', 'overline', 'line-through']
}
},
text: {
label: 'Text Content',
type: 'my-button-set',
group: 'text'
}
}
}
},
groups: {
'text-decoration': { label: 'Text Decoration (Select2)' },
'text': { label: 'Text' }
},
renderFieldContent: function(options, path, value, inspector) {
switch (options.type) {
case 'my-button-set': {
const buttonSetEl = document.createElement('div');
const yesEl = document.createElement('button');
const noEl = document.createElement('button');
const labelEl = document.createElement('label');
yesEl.textContent = 'Say YES!';
noEl.textContent = 'Say NO!';
labelEl.textContent = options.label || path;
buttonSetEl.appendChild(labelEl);
buttonSetEl.appendChild(yesEl);
buttonSetEl.appendChild(noEl);
buttonSetEl.dataset.result = value;
// When the user clicks one of the buttons, set the result to our field attribute
// so that we can access it later in `getFieldValue()`.
yesEl.addEventListener('click', function() {
buttonSetEl.dataset.result = 'YES';
inspector.updateCell(buttonSetEl, path, options);
});
noEl.addEventListener('click', function() {
buttonSetEl.dataset.result = 'NO';
inspector.updateCell(buttonSetEl, path, options);
});
return buttonSetEl;
}
case 'select2':
const $select = $('<select/>').width(170).hide();
$select.select2({ data: options.options }).val(value || 'none').trigger('change');
$select.data('select2').$container.css('margin', 10);
$select.on('change', function() {
inspector.updateCell(select[0], path, options);
});
const $wrapper = $('<div/>').append([
$('<label/>').text(options.label || path),
$select.data('select2').$container
]);
return $wrapper[0]
}
},
getFieldValue: function(attribute, type) {
if (type === 'my-button-set') {
return { value: attribute.dataset.result };
}
if (type === 'select2') {
const $select = $(attribute).find('.select2').data('element');
return { value: $select.val() };
}
}
});
}
It is also possible to customize the appearance and behavior of field labels in your Inspector. This can be used to create labels with custom HTML - as demonstrated by the myList
label (an <a>
tag with a <label>
tag inside), which links to an address specified in myList.url
. Additionally, our templating functionality can be used to define dynamic labels. We use this for myList.item
elements; when an element is added to the array with the +
button, the '{{index}}'
placeholder is replaced with the element's actual index within myList
.
joint.ui.Inspector.create('#container', {
cell: model,
inputs: {
myList: {
type: 'list',
url: 'https://jointjs.com',
item: {
type: 'text',
label: 'Item {{index}}'
}
}
},
renderLabel: function(opt, path) {
// returns an HTMLElement (`el`) as a custom label
// this method is called for every element inside inspector when being rendered
// in this case, it is called when the example loads, to create a label for the whole `myList`
// it is also called whenever the + button is pressed, to create a label for each element added to `myList`
const labelEl = document.createElement('label');
let el = labelEl;
// List numbering:
let text = opt.label;
const indexPlaceholder = '{{index}}';
// is this an inspector element with a `label` text specified (i.e. one of `myList` items)?
// does this inspector element contain a substring to replace?
if (text && text.indexOf(indexPlaceholder) > -1) {
// every input field in the inspector is addressed via a `path`
// elements added to `myList` are addressed as 'myList/0', 'myList/1', etc.
// we can use this in a regex to identify list elements
// we do this by checking if there is a '/' followed by a digit at the end of the path
const match = path.match(/\/(\d+)$/);
if (match) {
// if this is a list element, use its index as index
// (+1 to make sure the rendered labels start from 1)
const index = parseInt(match[1], 10) + 1;
// actually add the human-readable index to the label
text = text.replace(indexPlaceholder, index);
}
}
// then, set the text of the label to the text we generated
// else: set it to the raw `path` string
// (this happens for `myList` itself - so it gets a label that says 'myList')
labelEl.textContent = text || path;
// item labels are hidden via CSS by default, we need to unhide them
labelEl.style.cssText = 'display:block !important;';
// Clickable label:
// is this an inspector element with an `url` specified (i.e. `myList` itself)?
if (opt.url) {
// then create a new <a> element and add <label> to it
const aEl = document.createElement('a');
aEl.href = opt.url;
aEl.target = 'blank';
aEl.appendChild(labelEl);
el = aEl;
}
// we have created a custom label
return el;
}
});
The Inspector object triggers events when the user changes its inputs or when the Inspector needs to re-render itself partially. These events can be handled by using the Inspector on()
method.
render | Triggered when the Inspector re-renders itself partially. If you're adding event listeners or your own custom HTML into the inspector, you should always do it inside the `render` event handler. |
---|---|
change:[path to the changed property] | Triggered when the user changes a property of a cell through the inspector. The handler is passed the value at the property path and the input DOM element as arguments. |
close | Triggered when the inspector gets closed. |
create(container, options) | A helper for creating the inspector, where container is an HTMLElement or a selector (container is a DOM placeholder into which the Inspector will be rendered). For more information about options , see the Configuration chapter. An instance of joint.ui.Inspector is returned. |
---|---|
close() | A helper for closing Inspector instances which were created via the create() method above. |
render() | Render the Inspector based on the options passed to the constructor function. Note that this does not add the inspector to the live DOM tree. This must be done manually by appending the Inspector DOM element (accessible as its el property) to a live DOM element on the page. |
---|---|
updateCell() | Manually update the associated cell based on the current values in the Inspector. This is especially useful if you use the inspector with live mode disabled. See the Configuration chapter for more information. |
remove() | Remove the Inspector from the DOM. This should be called once you're finished with using the Inspector. |
focusField(path) | Focuses the field identified by the '/' separated path . |
openGroup(name) | Open the group identified by name . |
closeGroup(name) | Close the group identified by name . |
toggleGroup(name) | Toggle the group identified by name . |
openGroups() | Open all groups. |
closeGroups() | Close all groups. |
storeGroupsState() | Save the current group state - which groups are opened and which are closed. The key for storing the state is determined by the stateKey Inspector option. |
restoreGroupsState() | Apply the stored group state - open and close groups according this state. The state information is looked up by the current stateKey Inspector option. |
getGroupsState() | Get the current group state - array of closed groups. |
refreshSources() | Refresh sources of all controls. |
refreshSource(path) | Refresh source of a control by its path. |