%dialog-editor
is the component on the top of the hierarchy of the whole editor.
The overall dialog editor hierarchy is following:
%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 following:
'content': [{
'dialog_tabs': [{
...
'dialog_groups': [{
...
'dialog_fields': [...],
}],
}],
}],
%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 currently active tab in the activeTab
variable in DialogEditor
service.
function addTab
creates a new empty tab, pushes it to the array with 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: [],
}
removes the tab with ID sent in parameter and updates the activeTab
Because it's 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);
On the first level, the %dialog-editor-boxes
component iterates through all the tabs, selects the one that is active
and renders it's 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, position needs to be updated:
this.DialogEditor.updatePositions(
this.dialogTabs[this.DialogEditor.activeTab].dialog_groups
);
The Group is droppable
which means, 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
);
}
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 it's 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 defaul_value
can be reprented 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);
}
%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 it's own mehthods to load the data into this.modalData
. The source for the data is again 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 field.
The methods are shared with other controllers by binding them to the component's tempate in buildTemplate()
.
Displaying modal is done by showModal(options: any)
.
In dialog-editor-modal-tab
component, only label and description of the Dialog Tab is set
dialog-editor-modal-box
is also only used to set label and description of the Dialog Group
The main purpose of dialog-editor-modal-field
is similar as in %dialog-editor-field
component.
The component's template mostly consists ng-switch
, rendering templates of specific fields from 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.
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 Dialog Field is set as dynamic
, a new tab Overridable Options is displayed.
In Field Information all the fields must have a name
, that's unique in the dialog, optionally label
, description
, and as mentioned before, except Tag Control, all the fields can be set as dynamic
(boolean).
In Advanced tab, reconfigurable
(boolean) option can be set.
All the other parameters for the components, that can be set in Options or Overridable Options tab, through the modal are the following:
- 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)
- 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)
- 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)
- 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)
- 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)
- 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)
- 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)
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's special logic for the date and time parts because the
default_value
comes in as a string (cause it's just a JSON response), and then it gets parsed and separated into adateField
and atimeField
since the controls are separate.
dialog-editor-field-static
is component used for dragging the Dialog Fields placeholders into the droppable Dialog Group.
It's controller describes default values for parameters of each Dialog Fields.
Components Tree Selector and Tree View are set to be replaced by react-wooden-tree.
In the Dialog Editor are used for selecting path to Automate methods, using Dialog Editor HTTP service to lazy-load Automate methods.
DialogEditor
service's primary use is to store the data of the edited dialog in 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 than called in Dialog Editor controller
Often used function is public updatePositions(elements: any[])
used to recalculate indexes of items in the dialog after change of their position.
The Dialog Editor also uses sessionStorage to protect the users from mistakenly loosing 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)));
}
DialogValidation
service contains set of rules that needs to be fulfilled to enable 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
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 not ever :id
is 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 editor.html.haml template, where as well the ID of the dialog is stored by
ManageIQ.angular.app.value('dialogIdAction', '#{ dialog_id_action.to_json }');
Service Dialogs are serialized together from 4 database tables - dialog, dialog tab, dialog group, and dialog fields. The data are serialized into a single object, before 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 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
.
customizer