# Multi Select Form Control (v1.11+)

Source: https://docs.apimaker.dev/v1/extensions/ui_maker/form_controls/multi_select_form_control.html

> **UI Maker is deprecated**
>
> UI Maker is kept for the projects which use it and will be removed in API Maker v4. New admin screens are better built with any frontend framework on top of the [generated APIs](https://docs.apimaker.dev/v1/docs/apis-all/overview.html).

## Show multi select with database data

```typescript

let dbMasterConfig: T.IDBMasterConfig = {
    form: {
        fields: [
            [{
                label: 'Person',
                control: T.EDBMasterFormControl.multi_select,
                path: 'person_id',
                multiselectSettings: {
                    showClear: true,
                    display: 'chip',

                    dataSource: 'db_data',
                    dbData: { // Optional, it can pickup instance,database,collection details from schema.
                        collection: 'persons',
                        select: 'person_name, mobile_no, _id'
                    },
                    optionLabel: 'person_name', // 👈 It will be displayed in UI, HTML supported
                    optionValue: '_id', // 👈 It will be saved in database.

                    filter: true,
                    filterBy: 'person_name,mobile_no,_id',
                    // filterBy: 'person_name',
                    filterMatchMode: 'contains',
                    // virtualScroll: false,
                    alwaysGetLatestDataOnFormOpen: true,
                },
                validations: {
                    required: true,
                }
            }]
        ]
    }
};

```

## Show static data

```typescript

let dbMasterConfig: T.IDBMasterConfig = {
    form: {
        fields: [
            [{
                label: 'Gender',
                control: T.EDBMasterFormControl.multi_select,
                path: 'gender',
                multiselectSettings: {
                    showClear: true,

                    dataSource: 'static_data',
                    staticData: [{
                        label: 'Male', // Shown In UI
                        value: 'male', // Saved In DB
                        data: 'some other property data 1',
                    }, {
                        label: 'Female',
                        value: 'female',
                        data: 'some other property data 1',
                    }],
                    optionLabel: 'label', // 👈 It will be displayed in UI, HTML supported
                    optionValue: 'value', // 👈 It will be saved in database.

                    filter: true,
                    filterBy: 'label',
                    filterMatchMode: 'contains',
                },
                validations: {
                    required: true,
                }
            }]
        ]
    }
};

```

## Show data from custom API

```typescript

let dbMasterConfig: T.IDBMasterConfig = {
    form: {
        fields: [
            [{
                label: 'Cities',
                control: T.EDBMasterFormControl.multi_select,
                path: 'city_id',

                multiselectSettings: {
                    dataSource: 'api_call',
                    optionLabel: 'city_name',
                    optionValue: '_id',
                    apiCallOverrides: {
                        // :userPath = it will be replaced with admin user path by master page automatically.
                        // :beHostPort = it will be replaced with API Maker backend's protocol and host and port automatically. ex : https://example.com
                        // url: ':beHostPort/api/custom-api/:userPath/list-of-cities', // 👈 Use this to make it dynamic
                        url: 'http://localhost:38246/api/custom-api/admin/list-of-cities',
                    },
                    jsCode: [{
                        appendTo: T.EDBMasterMultiSelectAppendTo.modifyMultiSelectRequest,
                        code: `
                            reqBody.state_id = formData.state_id;
                            console.log(body);
                        `
                    }],
                },
            }]
        ]
    }
};

```

## Add new item support

```typescript

let dbMasterConfig: T.IDBMasterConfig = {
    form: {
        fields: [
            [{
                label: 'Product Categories',
                control: T.EDBMasterFormControl.multi_select,
                path: 'product_category_id',
                multiselectSettings: {
                    showClear: true,

                    dataSource: 'db_data',
                    dbData: {
                        collection: 'product_categories',
                        select: 'name'
                    },
                    optionLabel: 'name',
                    optionValue: '_id',

                    addNewFormConfig: { // 👈 Opens add product category & saves & reloads multi select
                        screenName: 'Product Category',
                        form: {
                            width: '500px',
                            fields: [
                                [{ // field
                                    label: 'Name',
                                    control: T.EDBMasterFormControl.input,
                                    path: 'name',
                                }]
                            ]
                        }
                    }
                },
                validations: {
                    required: true,
                }
            }]
        ]
    }
};

```

## Dependent Dropdowns | Auto Completes | Multi Selects

👉 It supports N level of dependent dropdowns | auto completes | multi selects with any type of complexity.

```typescript

let dbMasterConfig: T.IDBMasterConfig = {
    form: {
        fields: [
            [{
                path: 'planetId',
                control: T.EDBMasterFormControl.multi_select,

                multiselectSettings: {
                    showClear: true,
                    dataSource: 'db_data',
                    dbData: {
                        collection: 'ui_maker_planet',
                    },
                    optionValue: '_id',
                    optionLabel: 'name',

                    reloadDropdownsOfPath: ['continentId'], // 👈 dropdown | auto complete | multi select path
                },
            }],
            [{
                path: 'continentId',
                control: T.EDBMasterFormControl.multi_select,

                multiselectSettings: {
                    showClear: true,
                    dataSource: 'db_data',
                    dbData: {
                        collection: 'ui_maker_continent',
                    },
                    optionValue: '_id',
                    optionLabel: 'name',
                    isDependentOnPath: ['planetId'],
                    reloadDropdownsOfPath: ['countryId'], // 👈 dropdown | auto complete | multi select path

                    jsCode: [{
                        appendTo: T.EDBMasterMultiSelectAppendTo.modifyMultiSelectRequest,
                        code: `
                            reqBody.find.planetId = formData.planetId;
                        `,
                    }]
                }
            }],
            [{
                path: 'countryId',
                control: T.EDBMasterFormControl.multi_select,

                multiselectSettings: {
                    showClear: true,
                    dataSource: 'db_data',
                    dbData: {
                        collection: 'ui_maker_country',
                    },
                    optionValue: '_id',
                    optionLabel: 'name',
                    isDependentOnPath: ['continentId'],
                    reloadDropdownsOfPath: ['stateId'], // 👈 dropdown | auto complete | multi select path

                    jsCode: [{
                        appendTo: T.EDBMasterMultiSelectAppendTo.modifyMultiSelectRequest,
                        code: `
                            reqBody.find.continentId = formData.continentId;
                        `,
                    }]
                }
            }],
            [{
                path: 'stateId',
                control: T.EDBMasterFormControl.multi_select,

                multiselectSettings: {
                    showClear: true,
                    dataSource: 'db_data',
                    dbData: {
                        collection: 'ui_maker_states',
                    },
                    optionValue: '_id',
                    optionLabel: 'name',
                    isDependentOnPath: ['countryId'],

                    jsCode: [{
                        appendTo: T.EDBMasterMultiSelectAppendTo.modifyMultiSelectRequest,
                        code: `
                            reqBody.find.countryId = formData.countryId;
                        `,
                    }]
                }
            }],
        ]
    }
};

```

## Interface Documentation

```typescript
/**
 * Form field configuration for UI controls.
 * Defines the appearance, behavior, and validation of individual form inputs.
 */
export interface IDBMasterConfigFormField {
    /**
     * Unique identifier for programmatic element access.
     * Useful for dynamic field manipulation.
     */
    hiddenId?: string;
    /** Display label for the form field. */
    label?: string;

    /**
     * Help text displayed below the control.
     * Supports HTML formatting for rich content.
     */
    helpText?: string;

    /**
     * Database field path where the control value is stored.
     * Supports nested paths using dot notation.
     * @example 'name', 'address.city', 'user.contact.email'
     */
    path?: string;

    /** Type of UI control to render. */
    control?: EDBMasterFormControl;

    /**
     * CSS classes for the parent div wrapping the control.
     * @default 'col-lg mt-4 col-md-{calculated based on columns.length}'
     */
    cssClassDiv?: string;

    /**
     * Auto-focus this control when form opens.
     * @default false
     */
    autofocus?: boolean;

    /**
     * Disable the control or use expression to conditionally disable.
     * When a string is provided, it's evaluated as JavaScript.
     * @example true | false | "formData.type === 'readonly'"
     */
    disabled?: boolean | string;

    /**
     * Control visibility or use expression to conditionally show/hide.
     * When a string is provided, it's evaluated as JavaScript.
     * @default true
     * @example true | false | "formData.userRole === 'admin'"
     */
    visible?: boolean | string;

    /**
     * Nested form fields for complex layouts.
     * Enables hierarchical form structures within this field.
     */
    fields?: IDBMasterConfigFormField[][];

    /** Validation rules for this field. */
    validations?: Pick<IPropertyValidation, 'required'> & {
        /**
         * Dynamic required validation function.
         * Evaluated when form data changes to determine if field is required.
         * Note: When present, this takes precedence over static 'required' property.
         * @example "formData.type === 'individual' ? true : false"
         */
        requiredFun?: string;
    };
    /** Custom validation error messages. */
    validationErrors?: {
        /** Custom error message for required field validation. */
        required?: string;
    };

    /**
     * Multiselect control configuration.
     * Select multiple values from a list with chip-based display.
     *
     * **Data Sources:**
     * - **static_data**: Predefined options array
     * - **db_data**: Options from database collection/table
     * - **api_call**: Custom API endpoint for dynamic data
     *
     * **Key Features:**
     * - Multiple selection with checkboxes
     * - Selected items displayed as chips or comma-separated
     * - Searchable dropdown with filtering
     * - Select all / deselect all toggle
     * - Virtual scrolling for large datasets
     * - Selection limits and label overflow handling
     * - Option grouping support
     *
     * @example
     * // Basic multiselect with static data
     * multiselectSettings: {
     *     dataSource: 'static_data',
     *     staticData: skills,
     *     optionLabel: 'name',
     *     optionValue: 'id',
     *     display: 'chip',
     *     filter: true
     * }
     *
     * @example
     * // Database multiselect with selection limits
     * multiselectSettings: {
     *     dataSource: 'db_data',
     *     dbData: { collection: 'permissions' },
     *     optionLabel: 'name',
     *     display: 'chip',
     *     selectionLimit: 5,
     *     maxSelectedLabels: 3,
     *     selectedItemsLabel: '{0} permissions selected',
     *     showToggleAll: true
     * }
     *
     * @example
     * // Grouped multiselect options
     * multiselectSettings: {
     *     dataSource: 'static_data',
     *     staticData: groupedCities,
     *     optionLabel: 'name',
     *     optionGroupLabel: 'state',
     *     optionGroupChildren: 'cities',
     *     display: 'chip'
     * }
     */
    multiselectSettings?: {
        /** Give style object in angular style. */
        style?: any;

        /** custom CSS class to assign to control */
        cssClass?: string,

        placeholder?: string;
        showClear?: boolean;

        /** Minimum number of characters to initiate a search. */
        minLength?: number;

        /** Delay between keystrokes to wait before sending a query. */
        delay?: number;

        dataSource: 'static_data' | 'db_data' | 'api_call'; // custom_code = We can call any API in that.

        /** it will use used when dataSource is 'static_data'. */
        staticData?: any[]; // { label: string; value: any; }[] works default.

        /**
         * it can pickup IDB values from schema also.
         */
        dbData?: Partial<Pick<ICollectionIdentity, 'instance' | 'database' | 'collection' | 'table'>
            & Pick<IQueryFormat, 'find' | 'select' | 'limit' | 'deep' | 'sort'>>;

        /** Default : label */
        optionLabel?: string;

        /** Default : value */
        optionValue?: string;

        /** Whether to show the header. */
        showHeader?: boolean;

        /** Enable filter */
        filter?: boolean;

        /** one field or multiple comma separated fields are supported without any space in between. */
        filterBy?: string;

        /** Default: 'contains', Defines how the items are filtered.  */
        filterMatchMode?: 'endsWith' | 'startsWith' | 'contains' | 'equals' | 'notEquals' | 'in' | 'lt' | 'lte' | 'gt' | 'gte';

        /** Default : false, if true it will get latest data when form opens for add/edit operation. */
        alwaysGetLatestDataOnFormOpen?: boolean;

        /** Default : false, Make it true to handle huge amount of data. */
        virtualScroll?: boolean;

        /** Decides how many selected item labels to show at most. */
        maxSelectedLabels?: number;

        /** Decides how many items can be selected at most. */
        selectionLimit?: number;

        /** Ex: "{0} items selected", Label to display after exceeding max selected labels. defaults "ellipsis" keyword to indicate a text-overflow. */
        selectedItemsLabel?: string;

        /** Whether to show the checkbox at header to toggle all items at once. */
        showToggleAll?: boolean;

        /** Name of the disabled field of an option. */
        optionDisabled?: string;

        /** Name of the label field of an option group. */
        optionGroupLabel?: string;

        /** Name of the options field of an option group. */
        optionGroupChildren?: string;

        /** Advisory information to display in a tooltip on hover. */
        tooltip?: string;

        /** Type of CSS position. */
        tooltipPosition?: 'left' | 'top' | 'bottom' | 'right';
        tooltipStyleClass?: string;

        /**
         * Defines how the selected items are displayed.
         * @group Props
         */
        display: 'comma' | 'chip';

        /** on value change of current dropdown | auto complete | multi select, it will change values of these dropdowns | auto completes | multi selects and reload them. */
        reloadDropdownsOfPath?: string[];

        /** API call will happen when these values of path are present in formData */
        isDependentOnPath?: string[];

        addNewFormConfig?: IDBMasterConfig;

        apiCallOverrides?: IDBMasterAPICallOverrides;

        jsCode?: {
            /**
             * modifyDropdownRequest = It will run before hitting API call. So we can do whatever we want.<br/>
             * onceDropdownDataLoaded = Execute code when dropdown data is loaded.<br/>
             *
             * Available variables:<br/>
             * reqBody: IQueryFormat | any. Useful to modify apiCallOverrides also,<br/>
             * formData: any = Entire form object<br/>
             * column: IDBMasterConfigFormField = Configuration of that form column. column.dropdownSettings?.dbData?.find will be query to get data. <br/>
             * allDropdownDataMap: {[path: string]: any[]} = Map of all dropdown data<br/>
             * dropdownData: any[] = Latest loaded dropdown data<br/>
             * reloadDropdownsOfPath: string[] = Add path to this variable to reload its dropdown data.<br/>
             * globalData: any = User will send it using SET_GLOBAL_DATA_TO_USE_IN_ANY_SCRIPT event from parent.<br/>
             * utils: any = Common utility functions for user to use. <br/>
             * queryParams: any = Query params received from URL. <br/>
             */
            appendTo: EDBMasterMultiSelectAppendTo,
            /**
             * // dropdownData is available to use.
             *
             * // Return promise for long awaiting tasks.
             * new Promise(async (resolve, reject) => {
             *     await new Promise(r => setTimeout(r, 3000));
             *     dropdownData[0].name = 'Sample data';
             *     resolve();
             * });
             *
             * // Directly modify data of grid
             * dropdownData[0].name = 'Sample data';
             *
             * // Return function
             * (function setData() { dropdownData[0].name = 'Sample data'; } );
             *
             */
            code: string | (($scope: IDBMasterUIPageUtilsScope) => any),

        }[],

    };

}


/**
 * Validation rules for schema properties and form fields.
 * Define constraints that values must satisfy before being saved.
 */

export interface IPropertyValidation {
    /**
     * Field is mandatory and must have a non-null, non-empty value.
     * Applicable to all data types.
     */
    required?: boolean;
    /**
     * Minimum allowed value.
     * Applicable to: number, date
     */
    min?: number;
    /**
     * Maximum allowed value.
     * Applicable to: number, date
     */
    max?: number;
    /**
     * Minimum string/array length.
     * Applicable to: string, array
     */
    minLength?: number;
    /**
     * Maximum string/array length.
     * Applicable to: string, array
     */
    maxLength?: number;
    /**
     * Value must be unique across all records.
     * @deprecated API Maker maintains uniqueness internally.
     * Avoid using on tables with frequent updates due to performance impact.
     */
    unique?: boolean;
    /**
     * Validate email address format.
     * Applicable to: string
     */
    email?: boolean;
    /**
     * Custom validation function.
     * Return true if valid, false or error message if invalid.
     */
    validatorFun?: Function;

    /**
     * Enumeration constraint - value must be from this array.
     * @example ['active', 'inactive', 'pending']
     */
    enum?: any[];
}


export enum EDBMasterMultiSelectAppendTo {
    visible = 'visible',
    disabled = 'disabled',
    modifyMultiSelectRequest = 'modifyMultiSelectRequest',
    onceMultiSelectDataLoaded = 'onceMultiSelectDataLoaded',
    onChange = 'onChange',
    focus = 'focus',
    blur = 'blur',
    keyUp = 'keyUp',
    keyDown = 'keyDown',
    onClear = 'onClear',
    onSelectAllChange = 'onSelectAllChange',
}


/**
 * Available form control types for UI generation.
 * Each control type has specific settings and behavior.
 */
export enum EDBMasterFormControl {
    /** Single-line text input. */
    input = 'input',
    /** Numeric input with spinner buttons and formatting. */
    inputNumber = 'inputNumber',
    /** Text input with pattern-based masking (phone, SSN, etc.). */
    inputMask = 'inputMask',
    /** One-time password multi-character input. */
    inputOtp = 'inputOtp',
    /** Password input with visibility toggle and strength meter. */
    password = 'password',
    /** Date and/or time picker with calendar popup. */
    date_picker = 'date_picker',
    /** Multi-line text input. */
    textarea = 'textarea',
    /** Rich text WYSIWYG editor. */
    editor = 'editor',
    /** Binary checkbox (true/false). */
    checkbox = 'checkbox',
    /** Radio button group for single selection. */
    radio = 'radio',
    /** Color picker with multiple format support. */
    color_picker = 'color_picker',
    /** Dropdown select with single selection. */
    dropdown = 'dropdown',
    /** Autocomplete with type-ahead search. */
    auto_complete = 'auto_complete',
    /** Multi-select dropdown with chip display. */
    multi_select = 'multi_select',
    /** File upload with validation. */
    file_upload = 'file_upload',
    /** Nested data grid for one-to-many relationships. */
    grid = 'grid',
    /** Visual separator line. */
    divider = 'divider',
    /** Star-based rating input. */
    rating = 'rating',
    /** Circular dial for numeric input. */
    knob = 'knob',

    /** Collapsible accordion panels (field container). */
    accordion = 'accordion',
    /** Tabbed view for organizing fields (field container). */
    tab_view = 'tab_view',

    /** Clickable button with custom actions. */
    button = 'button',
    /** Image display with preview. */
    image = 'image',
    /** Custom HTML content. */
    customHTML = 'customHTML',
}

export enum EDBMasterCustomActionButtonAppendTo {
    click = 'click',
}

```
