Kaloyan Stamatov
Kaloyan Stamatov

Reputation: 4024

Aurelia, check when DOM is compiled?

How to check when DOM is compiled and inserted from Aurelia repeat cycle when the model is updated?

I have the following html:

<div clas="parent">
   <div class="list-group"> 
      <a repeat.for="$item of treeData">${$item.label}</a>
   </div>
</div>

Here I need to know when all <a> tags are listed in the DOM, in order to run jquery scroll plugin on the parent <div> container.

At first load, I do that from the attached() method and all is fine.

When I update the treeData model from a listener, and try to update the jquery scroll plugin, it looks that the DOM is not compiled, so my scroll plugin can not update properly.

If I put timeout with some minimum value like 200ms it works, but I don't think it is a reliable workaround.

So is there a way to solve that?

Thanks!

My View Model:

@customElement('tree-view')
@inject(Element, ViewResources, BindingEngine)
export class TreeView {
  @bindable data = [];
  @bindable filterFunc = null;
  @bindable filter = false;
  @bindable selectedItem;

  constructor(element, viewResources, bindingEngine) {
    this.element = element;
    this.viewResources = viewResources;
    this.bindingEngine = bindingEngine;
  }

  bind(bindingContext, overrideContext) {
    this.dataPropertySubscription = this.bindingEngine
      .propertyObserver(this, 'data')
      .subscribe((newItems, oldItems) => {
        this.dataCollectionSubscription.dispose();
        this._subscribeToDataCollectionChanges();
        this.refresh();
      });

    this.refresh();

    if (this.filter === true) {
      this.filterChanged(this.filter);
    }

    if (this.selectedItem) {
      this.selectedItemChanged(this.selectedItem);
    }
  }

  attached() {
     $(this.element).perfectScrollbar();
  }


  refresh() {
    this.treeData = processData(this.data, this.filterFunc);
    this.listItemMap = new WeakMap();
    this.treeData.forEach(li => this.listItemMap.set(li.item, li));
    this.filterChanged(this.filter);

    $(this.element).perfectScrollbar('update');
  }

This is only part of the code, but most valuable I think. I attach the jq plugin in attached function and try to update it in refresh function. In general I have listener that track model in other view, which then update that one without triggering bind method.

Upvotes: 1

Views: 364

Answers (2)

Fabio
Fabio

Reputation: 11990

You could push your code onto the microTaskQueue, which will schedule your function to be executed on the next event loop. For instance:

import { TaskQueue } from 'aurelia-task-queue';
//...
@inject(Element, ViewResources, BindingEngine, TaskQueue)
export class TreeView {

  constructor(element, viewResources, bindingEngine, taskQueue) {
    this.element = element;
    this.viewResources = viewResources;
    this.bindingEngine = bindingEngine;
    this.taskQueue = taskQueue;
  }

  refresh() {
    this.treeData = processData(this.data, this.filterFunc);
    this.listItemMap = new WeakMap();
    this.treeData.forEach(li => this.listItemMap.set(li.item, li));
    this.filterChanged(this.filter);

    // queue another task, which will execute after the tasks queued above ^^^
    this.taskQueue.queueMicroTask(() => {
       $(this.element).perfectScrollbar('update');
    });

  }
}

Upvotes: 0

Andrew
Andrew

Reputation: 794

An approach would be to use something called window.requestAnimationFrame (https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
In your view-model, when you modify your treeData array, try calling

window.requestAnimationFrame(()=>{
  $.fn.somePlugin();
});

Haven't tested this out, but based off what you're telling me, this might do what you need.

Upvotes: 1

Related Questions