Learning Aurelia question - dom operations in aurelia ux

I’m a bit new to Aurelia and am trying to learn the fundamentals of forms and data binding. I startedd looking into some examples like aurelia-ux, and noticed that a lot of the components do lots of dom operations and event handling within the viewmodel (.ts) file.

I was reading the docs though - and they generally seem to utilize the view engine to do stuff like keyup event binding and to use the for, if, etc helpers for rendering components. So my question is why does the aurelia-ux library do so much of this outside of the template and in the viewmodel?

for example in: https://github.com/aurelia/ux/blob/master/packages/form/src/ux-field.ts


    if (this.label && !this.element.querySelector('label')) {
      this.labelElement = document.createElement('label');
      this.labelElement.classList.add('ux-field__label');
      this.labelElement.textContent = this.label;

Thanks!

1 Like

As you can see from the template of the ux-field element:

<template>
  <slot></slot>
</template>

There’s no <label/>. The reason it cares is that it may or may not be used with a <label/>:

<!-- with a label element -->
<ux-field>
  <label>${some text}</label>
  <ux-input value.bind="..."></ux-input>
</ux-field>

<!-- without a label element -->
<ux-field label="Some ${label}">
  <ux-input value.bind="..."></ux-input>
</ux-field>

And the 2nd example (without label) is the reason why it does such thing in the view model. I’m guessing you are asking why it does so in such imperative way, there’s a more declarative way:

export class UxField {
  @child('label') labelEl: HTMLLabelElement;

  labelElChanged() {
    if (!this.labelEl) {
      // does not come with label, do something
    }
  }
}

But the above won’t play nicely with shadow DOM emulation mode, which is what the <ux-field/> is using, where it won’t fire labelElChanged, if not using shadowDOM, without a mutation:

<!-- will call labelElChanged() -->
<ux-field>
  <label>${some text}</label>
  <ux-input value.bind="..."></ux-input>
</ux-field>

<!-- wont call labelElChanged() -->
<ux-field>
  <ux-input value.bind="..."></ux-input>
</ux-field>

So it’s using manual detection as a safe way to handle such scenario of ensuring there’s always a <label/>. It’s not ideal, but since we haven’t had complains around this, it’s does not seem to give signal whether a fix is needed, since normally you don’t do this. The fix for this, may or may not be simple, because without shadow DOM, there’s no clear boundary to tell whether child elements come from content or inner template.

Edit: reading the code of templating, I think it could be a not-complicated fix, so maybe there’ll be a PR

1 Like

Thanks for the info! That helps a lot. Yes, my question was not worded very well but you figured out exactly what I was asking. Really just why use imperative code when you have the declarative templates.

What I’m hearing is that this isn’t the normal way to do things but since we needed a way to allow the component to use a label in a slot or a label from the property the custom code allows both.

For example, you could do something like this:

<template>
<label if.bind="label">${label}</label>
<slot></slot>

I suppose the issue is that if the user does this you will have two labels:

<ux-field label.bind="label text">
<label>label text</label>
</ux-field>

Its pretty interesting actually that aurelia lets you do this sort of customization - on one hand you have multiple ways to handle the problem but on the other its flexible enough to let you do custom stuff like this.

1 Like

Ncice follow up post :+1:

Its pretty interesting actually that aurelia lets you do this sort of customization - on one hand you have multiple ways to handle the problem but on the other its flexible enough to let you do custom stuff like this.

Aurelia does not assume its the sole owner of the page, or the elements in your page, so the operation of adding/removing/updating elements is quite compatible with many ways we would “trash” the html.

1 Like