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 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 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 currently active tab in the activeTab variable in DialogEditor service.

addTab()

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: [],
}

removeTab(id: number)

removes the tab with ID sent in parameter and updates the activeTab

Update positions of tabs

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);

Dialog Groups

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
  );
}

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 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);
}

%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).

Tab Modal

In dialog-editor-modal-tab component, only 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 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.

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 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:

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 if validatior_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 if validatior_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 if validatior_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 if validatior_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)
  • 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, and default_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 a dateField and a timeField since the controls are separate.

Toolbox

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.

Tree Selector and Tree View

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.

Angular Services

Dialog Editor

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)));
}

Dialog Editor Validation

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

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 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 }');

Data from the API

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.

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 how 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.

The demo is not anyhow connected to a real instance and so there’s no way to connect dynamic fields to Automate method