Dialog Editor
Angular components
Dialog Editor
%dialog-editor
is the component on the top of the hierarchy of the whole editor.
The overall dialog editor hierarchy is as follows:
%dialog-editor
%dialog-editor-tabs
%dialog-editor-boxes
| %dialog-editor-field
%dialog-editor-modal -# modal for editing properties of field/tab/box
| %dialog-editor-modal-tab
| %dialog-editor-modal-box
| %dialog-editor-modal-field
| %dialog-editor-modal-field-template -# per field type
The structure of JSON object used to describe the service dialogs is as follows:
'content': [{
'dialog_tabs': [{
...
'dialog_groups': [{
...
'dialog_fields': [...],
}],
}],
}],
Dialog Tabs
%dialog-editor-tabs
displays a list of tabs assigned to the dialog.
It is the main component as all the content is stored inside the object.
At the beginning, the component’s controller loads the tabs of the dialog:
this.tabList = this.DialogEditor.getDialogTabs();
and assigns id of the currently active tab in the activeTab
variable in the DialogEditor
service.
addTab()
function addTab
creates a new empty tab, pushes it to the array with the other tabs, and updates activeTab
to the new tab.
{
description: __('New tab ') + nextIndex,
display: 'edit',
label: __('New tab ') + nextIndex,
position: nextIndex,
active: true,
dialog_groups: [],
}
removeTab(id: number)
removes the tab with the ID sent in parameter and updates the activeTab
.
Update positions of tabs
Because it is possible to remove (or just move the position) tab in the middle of the list, after every change the positions are updated by calling updatePosition
method defined in the Dialog Editor
service:
this.DialogEditor.updatePositions(this.tabList);
Dialog Groups
On the first level, the %dialog-editor-boxes
component iterates through all the tabs, selects the one that is active
, and renders its groups:
ng-repeat='tab in vm.dialogTabs'
ng-if='tab.position === vm.service.activeTab'
After, it iterates through all the tabs, and calls %dialog-editor-field
component belonging to the group (decided by the position of the group).
Working with groups is very similar to working with tabs – addBox()
, removeBox(id: number)
have the same purpose.
After every change, the position needs to be updated:
this.DialogEditor.updatePositions(
this.dialogTabs[this.DialogEditor.activeTab].dialog_groups
);
The Group is droppable
which means that elements can be Drag&Drop-ed into the content of the group.
Therefore in the controller of the component handling for updating position of dialog fields needs to be done:
public droppableOptions(e: any, ui: any) {
const elementScope: any = ng.element(e.target).scope();
let droppedItem: any = elementScope.dndDragItem;
let droppedPlace: any = elementScope.box;
// update name for the dropped field
if (!_.isEmpty(droppedItem)) {
this.updateFieldName(droppedItem);
}
// update indexes of other boxes after changing their order
this.DialogEditor.updatePositions(
droppedPlace.dialog_fields
);
}
Dialog Fields
The most important part of %dialog-editor-field
component is ng-switch
in the template of the component, that renders the dialog field according to its type (on="vm.fieldData.type"
).
All the possible fields and their parameters are listed in this component.
The dialog field types are:
- Text Box
- Text Area
- Check Box
- Date Control
- Date Time Control
- Dropdown List
- Radio Button
- Tag Control
In case of Dropdown, the default_value
can be represented either as an array (if the Dropdown is multiselect) or a string. For multiselect dropdowns there is a method for converting the default_value
attribute:
public convertValuesToArray() {
this.fieldData.default_value = angular.fromJson(this.fieldData.default_value);
}
Modal
%dialog-editor-modal
does not contain any template for the component. Instead it contains behavior for modal used to edit details for Tabs, Groups and Fields.
The primary function of the component is to load data it needs. Each type has its own mehthods to load the data into this.modalData
. The source for the data is again the commonly used DialogEditor
service method: this.DialogEditor.getDialogTabs()
.
public loadModalTabData(tab: number)
public loadModalBoxData(tab: number, box: number)
public loadModalFieldData(tab: number, box: number, field: number)
Modal controller also contains methods addEntry()
and removeEntry()
that are specific for Dropdown List or Radio Button component.
resolveCategories()
, setupCategoryOptions()
and currentCategoryEntries()
are methods specific for Tag Control fields.
The methods are shared with other controllers by binding them to the component’s template in buildTemplate()
.
Displaying the modal is done by showModal(options: any)
.
Tab Modal
In dialog-editor-modal-tab
component, only the label and description of the Dialog Tab is set.
Group Modal
dialog-editor-modal-box
is also only used to set label and description of the Dialog Group.
Field Modal
The main purpose of dialog-editor-modal-field
is similar to the %dialog-editor-field
component.
The component’s template mostly consists of ng-switch
, which renders templates of specific fields from the modal-field-template
component described lower, with necessary methods passed into the component through the binding described in %dialog-editor-modal
component.
Example for the Radio Button Dialog Field:
<dialog-editor-modal-field-template
ng-switch-when="DialogFieldRadioButton"
template="radio-button.html"
show-fully-qualified-name="vm.showFullyQualifiedName"
tree-options="vm.treeOptions"
modal-tab-is-set="vm.modalTabIsSet"
modal-tab="vm.modalTab"
add-entry="vm.addEntry"
remove-entry="vm.removeEntry"
modal-data="vm.modalData">
Label and Description for the Dialog Field are also set in this component, as well as setting Dialog Field to be Dynamic or Reconfigurable, as these properties are same for all the fields except Tag Control types.
Field Template
Controller of dialog-editor-modal-field-template
contains mostly bindings to the methods related to specific components.
The main part of the component are templates for each of the dialog fields. All the parameters for Dialog Fields are described in the templates of this component.
The modal for Dialog Fields contains three permanent tabs - Field Information, Options, Advanced. If the Dialog Field is set as dynamic
, a new tab with an Overridable Options heading is displayed.
In Field Information all the fields must have a name
that is unique in the dialog, optionally a label
, description
, and (as mentioned before, except Tag Control) all the fields can be set as dynamic
(boolean).
In the Advanced tab, the reconfigurable
(boolean) option can be set.
All the other parameters for the components the can be set in Options or Overridable Options tab through the modal are as follows:
Text Box
- if
dynamic
is not checked:data_type
(select - string / integer)default_value
(string)dialog_field_responders
(multiple select - dynamic fields list)options.protected
(boolean) – for passwords, value will be replaced with*
required
(boolean)read_only
(boolean)validator_type
(‘regex’ (string) / false (boolean))validator_rule
(string; only ifvalidatior_type
has a value'regex'
)visible
(boolean)
- if the field is
dynamic
:data_type
(select - string / integer)dialog_field_responders
(multiple select - dynamic fields list)load_values_on_init
(boolean)options.protected
(boolean) – for passwords, value will be replaced with*
required
(boolean)resource_action
(path to automate method)show_refresh_button
(boolean)validator_type
(‘regex’ (string) / false (boolean))validator_rule
(string; only ifvalidatior_type
has a value'regex'
)- In Overridable Options tab:
default_value
(string)read_only
(boolean)visible
(boolean)
Text Area
- if
dynamic
is not checked:default_value
(string)dialog_field_responders
(multiple select - dynamic fields list)required
(boolean)read_only
(boolean)validator_type
(‘regex’ (string) / false (boolean))validator_rule
(string; only ifvalidatior_type
has a value'regex'
)visible
(boolean)
- if the field is
dynamic
:dialog_field_responders
(multiple select - dynamic fields list)load_values_on_init
(boolean)required
(boolean)resource_action
(path to automate method)show_refresh_button
(boolean)validator_type
(‘regex’ (string) / false (boolean))validator_rule
(string; only ifvalidatior_type
has a value'regex'
)- In Overridable Options tab:
default_value
(string)read_only
(boolean)visible
(boolean)
Check Box
- if
dynamic
is not checked:default_value
(boolean)dialog_field_responders
(multiple select - dynamic fields list)required
(boolean)read_only
(boolean)visible
(boolean)
- if the field is
dynamic
:dialog_field_responders
(multiple select - dynamic fields list)load_values_on_init
(boolean)required
(boolean)resource_action
(path to automate method)show_refresh_button
(boolean)- In Overridable Options tab:
read_only
(boolean)visible
(boolean)
Date Control
- if
dynamic
is not checked:default_value
(Date object, after #373)dialog_field_responders
(multiple select - dynamic fields list)required
(boolean)read_only
(boolean)show_past_dates
(boolean)visible
(boolean)
- if the field is
dynamic
:dialog_field_responders
(multiple select - dynamic fields list)options.show_past_dates
(boolean)required
(boolean)resource_action
(path to automate method)show_refresh_button
(boolean)- In Overridable Options tab:
read_only
(boolean)visible
(boolean)
Date Time Control
- if
dynamic
is not checked:default_value
(Date object, after #373)dialog_field_responders
(multiple select - dynamic fields list)required
(boolean)read_only
(boolean)show_past_dates
(boolean)visible
(boolean)
- if the field is
dynamic
:dialog_field_responders
(multiple select - dynamic fields list)options.show_past_dates
(boolean)required
(boolean)resource_action
(path to automate method)show_refresh_button
(boolean)- In Overridable Options tab:
read_only
(boolean)visible
(boolean)
Dropdown List
- if
dynamic
is not checked:data_type
(select - string / integer)default_value
(string, or a string representing an array for multiselect –default_value: "[\"1\", \"2\"]"
)dialog_field_responders
(multiple select - dynamic fields list)options.force_multi_value
(boolean)options.sort_by
(select - none / value / description)options.sort_order
(select - ascending / descending)required
(boolean)read_only
(boolean)visible
(boolean)values
(array, [0] - value, [1] - key)
- if the field is
dynamic
:data_type
(select - string / integer)dialog_field_responders
(multiple select - dynamic fields list)load_values_on_init
(boolean)options.force_multi_value
(boolean)required
(boolean)resource_action
(path to automate method)show_refresh_button
(boolean)- In Overridable Options tab:
options.sort_by
(select - none / value / description)options.sort_order
(select - ascending / descending)read_only
(boolean)visible
(boolean)
Radio Button
- if
dynamic
is not checked:data_type
(select - string / integer)default_value
(string or array for multiselect)dialog_field_responders
(multiple select - dynamic fields list)options.sort_by
(select - none / value / description)options.sort_order
(select - ascending / descending)required
(boolean)read_only
(boolean)visible
(boolean)values
(array, [0] - value, [1] - key)
- if the field is
dynamic
:data_type
(select - string / integer)dialog_field_responders
(multiple select - dynamic fields list)load_values_on_init
(boolean)required
(boolean)resource_action
(path to automate method)show_refresh_button
(boolean)- In Overridable Options tab:
options.sort_by
(select - none / value / description)options.sort_order
(select - ascending / descending)read_only
(boolean)visible
(boolean)
Tag Control
data_type
(select - string / integer)dialog_field_responders
(multiple select - dynamic fields list)options.category_id
(select - list of categories)options.force_single_value
(boolean)options.sort_by
(select - none / value / description)options.sort_order
(select - ascending / descending)required
(boolean)read_only
(boolean)visible
(boolean)
*A note by @eclarizio related to accessing values of the fields:
on the ui-components side for most of the field types,
default_value
is the value that is getting passed back from the refresh API call that we should be looking at to determine what to show to the user after a refresh happens. We changed it in 7dcb1f7. For sorted items,values
is the key we use since it needs to be a list, anddefault_value
is simply the one that is selected from that list.On the ui-components side, because of the way datetime controls work, there is special logic for the date and time parts because the
default_value
comes in as a string (because it is just a JSON response), and then it gets parsed and separated into adateField
and atimeField
since the controls are separate.
Toolbox
dialog-editor-field-static
is the component used for dragging the Dialog Fields placeholders into the droppable Dialog Group.
Its controller describes default values for parameters of each Dialog Field.
Tree Selector and Tree View
Components Tree Selector and Tree View are set to be replaced by react-wooden-tree.
In the Dialog Editor text boxes are used for selecting path to Automate methods using Dialog Editor HTTP service to lazy-load Automate methods.
Angular Services
Dialog Editor
The DialogEditor
service’s primary use is to store the data of the edited dialog in the setData
function:
public setData(data: any) {
this.data = data;
// the dialog data are now stored in this.data.content[0]
// as indicated earlier in the document
...
}
The setData
function is then called in Dialog Editor controller
Another often used function is public updatePositions(elements: any[])
used to re-calculate indexes of items in the dialog after a change of their position.
The Dialog Editor also uses sessionStorage to protect the users from mistakenly losing the changes while editing the dialog. In the sessionStorage
the dialogs are stored by identificator 'service_dialog-' + id
.
public clearSessionStorage(id: string) {
sessionStorage.removeItem(this.sessionStorageKey(id));
}
public backupSessionStorage(id: string, dialog: any) {
sessionStorage.setItem(this.sessionStorageKey(id), JSON.stringify(dialog));
}
public restoreSessionStorage(id: string) {
return JSON.parse(sessionStorage.getItem(this.sessionStorageKey(id)));
}
Dialog Editor Validation
The DialogValidation
service contains set of rules that needs to be fulfilled in order to be able to submit the Dialog.
The rules are:
- rules for Dialog:
- Dialog needs to have a label
- Dialog needs to have at least one tab
- rules for Dialog Tabs:
- Dialog tab needs to have a label
- Dialog tab needs to have at least one group
- rules for Dialog Groups:
- Dialog group needs to have a label
- Dialog group needs to have at least one field
- rules for Dialog Fields:
- Dialog field needs to have a name
- Dialog field needs to have a label
- Dropdown needs to have entries
- Category needs to be set for TagControl field
- Entry Point needs to be set for Dynamic elements
- If the value is set as Integer, entered values must be a number
Workflow
Launch the action to open Dialog Editor
The Dialog Editor can be opened for three different actions - edit
, copy
or new
.
If both :id
and :copy
keys are specified, action is copy
. If the :copy
key is missing, the action is edit
, if ever :id
is not present, a new
Dialog is being created.
The behavior is described in miq_ae_customization_helper.rb
The top level component %dialog-editor
is used in the editor.html.haml template, where the ID of the dialog is also stored by:
ManageIQ.angular.app.value('dialogIdAction', '#{ dialog_id_action.to_json }');
Data from the API
Service Dialogs are serialized together from four database tables - dialog, dialog tab, dialog group, and dialog fields. The data are serialized into a single object, before being passed to the Dialog Editor.
The data are loaded by Dialog Editor HTTP service from API by the request:
return API.get('/api/service_dialogs/' + id + '?attributes=content,buttons,label');
or in case of a new Dialog, an empty dialog structure, defined in Dialog Editor controller is used:
var dialogInitContent = {
'content': [{
'dialog_tabs': [{
'label': __('New tab'),
'position': 0,
'dialog_groups': [{
'label': __('New section'),
'position': 0,
'dialog_fields': [],
}],
}],
}],
};
from the API the data are received from the method fetch_service_dialogs_content
:
def fetch_service_dialogs_content(resource)
target, resource_action = validate_dialog_content_params(params)
resource.content(target, resource_action, true)
end
after JSON is loaded for dynamic fields, dialog_field_responders needs to have an id, customized by: translateResponderNamesToIds
.
Submitting the Dialog
Before sumbitting the Dialog, a keys used by Angular need to be removed from the object describing the Dialog content. For that a customizer
function is used.
After, the Dialog is saved by calling:
return API.post('/api/service_dialogs' + id, { action: action, resource: data }, { skipErrors: [400] });
Demo
To play with the Dialog Editor and see what the JSON object of the dialog looks like, you can run ui-components and play with the Dialog Editor at http://localhost:4000/#/dialog/editor but please remember that
The demo is not connected to a real instance and there is no way to connect dynamic fields to Automate methods in it.