Nandhakumar S
Nandhakumar S

Reputation: 61

Creating a Custom Dropdown Menu Appended to the Editor Body

In angular 17 using ngx-editor. How to Create a custom dropdown menu in editor menu that appends to the editor body?
For example, the dropdown(email address) and options are [email protected], [email protected], and [email protected]. When any one is chosen, its text will be displayed in the editor body.

like below image email address,
enter image description here

I tired the following,

In HTML,

<div class="editor">
   <ngx-editor-menu [editor]="options" [toolbar]="toolbar"></ngx-editor-menu>
   <ngx-editor [editor]="options" formControlName="BODY"></ngx-editor>
</div

In Typescript:

import { Editor, TBItems, Toolbar, ToolbarCustomMenuItem, ToolbarDropdown, ToolbarDropdownGroupKeys, ToolbarItem, Validators } from 'ngx-editor';

@Component({
  selector: 'app-template',
  templateUrl: './template.component.html',
  styleUrls: ['./template.component.scss']
})

export class TemplateComponent implements OnInit {
 public modalData:any;
 options: Editor = new Editor();
 toolbar: Toolbar = [];
 customDropdown: TBItems[];
 public pluginsToCreate:any[] = [];
 public formGroup: FormGroup;

 constructor(private fb: FormBuilder) { }

 ngOnInit(): void {
 this.formGroup = this.fb.group({ 
  BODY: [this.modalData.mailData.MESSAGE, Validators.required()]
 })
  const dropdownOptions: any= this.modalData.customer.map((option: any) => ({ value: option.DATA, label: option.TITLE }));
    if (dropdownOptions.length > 0) {
      this.customDropdown =  [dropdownOptions] ;
    }
    this.toolbar = [
      ['bold', 'italic'],
      ['underline', 'strike'],
      ['code', 'blockquote'],
      ['ordered_list', 'bullet_list'],
      [{ heading: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] }],
      ['link', 'image'],
      ['text_color', 'background_color'],
      ['align_left', 'align_center', 'align_right', 'align_justify'],
      ['horizontal_rule'],
      this.customDropdown
    ];
  }
}

Upvotes: 1

Views: 596

Answers (1)

LuisPM
LuisPM

Reputation: 25

I recently had to implement this, it was hard to start at first because the Ngx-Editor documentation is brief, but after doing further reading i was able to get it working. I want to preface that im not a Ngx-Editor / Prosemirror Expert and there may be a better way of doing it.

<ngx-editor-menu /> takes a binding named [customMenuRef], in this binding you will pass a ref to a custom component that you want to display in the menu bar. You also need to pass the Editor to your custom component to have access to the Editor's state and position for dynamic inserting. You named your editor instance as options so your html template for TemplateComponent should look something like this:

<div class="editor">
   <ngx-editor-menu  [editor]="options"
                    [toolbar]="toolbar"
                    [customMenuRef]="customDropDownMenu"></ngx-editor-menu>
   <ngx-editor [editor]="options" formControlName="BODY"></ngx-editor>
</div>

<!-- Custom  -->
<ng-template #customDropDownMenu>
    <my-custom-dropdown-menu [editor]="options"></my-custom-dropdown-menu>
</ng-template>

Then your custom dropdown component should look something like what's below, in this component we will access the Editor properties to find its current state and cursor location to update with the new selection. I don't know if you are using a styling library or not, the template in the custom ref component will be an angular dropdown menu component for the sake of creating a simplicity. but you can modify it to your needs.

Also you state you are using Angular 17, so we will be using the new Control Flow Blocks which use @For {} instead of *ngFor directive.

@Component({
    selector: 'my-custom-dropdown-menu',
    template: `
        <div class="NgxEditor__Seperator"></div>
        <div>
            <button type="button" mat-button [matMenuTriggerFor]="appMenu">
                Insert Emails
            </button>
            
            <mat-menu #appMenu>
                @for (emailOption of emailOptions; track emailOption) {
                    <button type="button" (click)="insertEmail(emailOption)">{{ emailOption }}</button>
                }
            </mat-menu>
        </div>
    `
})
export class MyCustomDropdownMenuComponent {
    @Input editor: Editor; // the editor reference being passed

    public readonly emailOptions: string[] = ["[email protected]", "[email protected]", "[email protected]"];    

insertEmail(email: string): void { 
    const {state, dispatch} = this.editor.view;  
    const {tr, selection} = state;  
    const insertPosition = selection.$from.pos;  
    dispatch(tr.insertText(email, insertPosition));
}

}

The method insertEmail() in your CustomDropdownMenuComponent takes in a string that will be our email to insert. the logic of the method is to destructure the state and dispatch objects from the editor reference we passed to our component.

Then from the state object we destructure again to get the tr and selection object. we then get the current cursor position from the selection object and assign it to our variable insertPosition, then we use the dispatch method to update the editor state and insert our email param.

I'll create a stackblitz later on. in the meantime i hope this helps.

Upvotes: 0

Related Questions