How to initialize components in vaadin-dialog

498 views Asked by At

I am trying to rewrite Vaadin 6 application (using complex dialog windows) to Vaadin Fusion 22. Having problems initializing dialogs. For example set up i18n in the DatePicker. TestView.datePicker is initialized on firstUpdated(), but TestDateDialog.datePicker is always undefined or null.

test-view.ts

@customElement('test-view')
export class TestView extends View {

    @state()
    private dialogOpened = false;

    @query('#date1')
    private datePicker?: DatePicker;

    @query('#test-date')
    private dialog?: TestDateDialog;

    render() {
        return html`<vaadin-vertical-layout class="w-full h-full" theme="padding">
                    <vaadin-date-picker id="date1">
                    </vaadin-date-picker>
                    <vaadin-button @click=${this.openDialog}>Open dialog</vaadin-button>
                </vaadin-vertical-layout>
      <test-date-dialog id="test-date" .dialogOpened="${this.dialogOpened}" @test_dialog_closed="${this.onDialogOpenedChanged}" ></test-date-dialog>
     `;
    }

    protected firstUpdated() {
        this.initDates();
    }

    private initDates() {
        const i18n = {
            today: '___TODAY___',
        };
        if (this.datePicker) {
            this.datePicker.i18n = {
                ...this.datePicker.i18n,
                ...i18n
            }
        }
    }

    private openDialog() {
        this.dialogOpened = true
        if(this.dialog){
            this.dialog.initDates() //1 Is this okay?
        }
    }

    onDialogOpenedChanged(e: CustomEvent) {
        this.dialogOpened = e.detail.value;
    }

test-date-dialog.ts

@customElement('test-date-dialog')
export class TestDateDialog extends View {

    @state()
    private dialogOpened = false;

    @query('#date11')
    private datePicker?: DatePicker;

    render() {
        return html`      
      <vaadin-dialog id="testD" no-close-on-outside-click no-close-on-esc  .opened=${this.dialogOpened}
         @opened-changed="${(e: CustomEvent) => this.openDialog(e.detail.value)}"        
        .renderer="${guard([], () => (root: HTMLElement) => {
            render(html`<vaadin-vertical-layout>
                 <vaadin-horizontal-layout class="w-full">                   
                    <vaadin-button theme="icon tertiary contrast" @click="${() => this.cancel()}">
                    <vaadin-icon slot="prefix" icon="vaadin:close-big"></vaadin-icon></vaadin-button>
                </vaadin-horizontal-layout>
               <vaadin-date-picker id="date11">
                                </vaadin-date-picker>
              </vaadin-vertical-layout>
            `, root);
        })}"></vaadin-dialog>    
     `;
    }

    private openDialog(open: boolean) {
        this.dialogOpened = open;
        //if (open) {
            //this.initDates(); //2   Is this okay?
        //}
    }

    //protected firstUpdated() {
    //    this.initDates(); 
    //}

    initDates() {
        console.log(this.shadowRoot?.querySelector('#date11')) //undefined
        console.log(this.querySelector('#date11')) //null
        console.log(this.datePicker)  //null

        const i18n = {
            today: '___TODAY___'
        };
        if (this.datePicker) {
            this.datePicker.i18n = {
                ...this.datePicker.i18n,
                ...i18n
            }
        }
    }

    cancel() {
        const options = {
            detail: {
                opened: false
            },
            bubbles: true,
            composed: true
        };
        this.dispatchEvent(new CustomEvent<CancelButtonEvent>('test_dialog_closed', options));

        this.dialogOpened = false;
    }

How to do some initialization work (with asynchronous backend calls) after opening a dialog, not only with @property, but also with components via @query?

I have little experience in JS / TS / Lit, maybe I'm doing something wrong.

1

There are 1 answers

2
Sascha Ißbrücker On BEST ANSWER

The @query decorator won't work with dialog content generated by the renderer function, because all the dialog content is transported into a separate overlay element, which is not a child of your test-date-dialog. You can confirm this by inspecting the dialog content in your browsers dev tools for example. The @query decorator doesn't work in that case, because it only looks for children of the element on which it is applied.

One solution here could be to extract the dialog content from the renderer function into a separate Lit component (for example TestDateDialogContent), and render that component in the renderer function. Within TestDateDialogContent you can then use the @query decorator to access the date picker. You can also use the regular lifecycle methods, such as firstUpdated, to do other initialization, such as setting the I18N settings on the date picker, or making async calls to the backend.

In test-date-dialog-content.ts:

@customElement('test-date-dialog-content')
class TestDialogContent extends LitElement {
  @query('#date11')
  private datePicker!: DatePicker;

  protected firstUpdated() {
    this.initDates();
  }

  private initDates() {
    const i18n = {
      today: '___TODAY___'
    };
    this.datePicker.i18n = {
      ...this.datePicker.i18n,
      ...i18n
    };
  }

  render() {
    return html`
      <vaadin-vertical-layout>
        ...
        <vaadin-date-picker id="date11"></vaadin-date-picker>
        ...
      </vaadin-vertical-layout>
    `;
  }
}

In test-date-dialog.ts:

<vaadin-dialog
  .renderer="${guard([], () => (root: HTMLElement) => render(html` <test-date-dialog-content></test-date-dialog-content>`, root))}"
>
</vaadin-dialog>

Ideally you want to avoid accessing individual elements like the date picker imperatively as much as possible, and use Lit property bindings instead (<some-element .some-property="${this.someValue}">). However in this case, where you want to extend the date picker's I18N settings with a few custom values, that is unavoidable. In combination with the dialog transporting its contents into a separate element, that makes this use-case a bit more complicated.