4

In my template, I have a div I want to use as a tooltip of sorts. When I have a model selected, the tooltip shows, and then I use tether to put it in the correct spot. If I set tether immediately after setting the model that makes the element show, it's size isn't computed properly and Tether doesn't properly limit constraints. If I debounce it with a setTimeout, I can get it in the right place, but that feels hokey. My question:

Is there some sort of callback mechanism I can attach to that is called after show.bind has made the element visible?

My template:

<div ref="tooltip" show.bind="selectedAlert" class="homepage-stats-tooltip panel panel-default">
    <div class="panel-body">
        <h1>${selectedAlert.Name}</h1>
        <dl>
            <dt>Normal</dt>
            <dd>${selectedAlert.NormalVolume}</dd>
            <dt>Current</dt>
            <dd>${selectedAlert.CurrentVolume}</dd>
        </dl>
    </div>
</div>

The function that sets the model and calls Tether:

showTip(event, state) {
    if (!state) {
        return;
    }

    console.log(`Show tip for ${state.Name}.`);
    this.selectedAlert = state;

    setTimeout(() => {
        new Tether({
            element: this.tooltip,
            target: event.target,
            attachment: "top left",
            targetAttachment: "top right",
            constraints: [
                {
                    to: this.usMap,
                    pin: true,
                    attachment: 'together'
                }
            ]
        });
    }, 10);
}

Thanks!

Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
Jereme
  • 1,445
  • 1
  • 16
  • 32
  • 1
    Possible duplicate of [@bindable changeHandler fires before bindings are done updating](http://stackoverflow.com/questions/35587033/bindable-changehandler-fires-before-bindings-are-done-updating) – Fabio Mar 17 '16 at 00:18

1 Answers1

5

Changes to the DOM such as a show triggered by changes to the selectedAlert property are enqueued on aurelia's micro task queue. This has the effect of batching DOM changes, which is good for performance. You can enqueue your own tasks on the micro task queue and they will execute after the element has become visible:

import {TaskQueue} from 'aurelia-task-queue';

@inject(TaskQueue)
export class MyComponent {
  constructor(taskQueue) {
    this.taskQueue = taskQueue;
  }

  showTip(event, state) {
    if (!state) {
        return;
    }

    console.log(`Show tip for ${state.Name}.`);
    this.selectedAlert = state; // <-- task is queued to notify subscribers (eg the "show" binding command) that selectedAlert changed

    // queue another task, which will execute after the task queued above ^^^
    this.taskQueue.queueMicroTask(() => {
        new Tether({
            element: this.tooltip,
            target: event.target,
            attachment: "top left",
            targetAttachment: "top right",
            constraints: [
                {
                    to: this.usMap,
                    pin: true,
                    attachment: 'together'
                }
            ]
        });
    });
  }

}
Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
  • Just a question about your answer: showTip never gets called in code, does this mean the task is already bound while compiling the code, does the binding to the microtask happen another way, or did you miss that? – Randy Mar 17 '16 at 13:30
  • Thank you much! Exactly what I was looking for. – Jereme Mar 17 '16 at 18:14
  • In my code, showTip is bound to an SVG path, but it could be cound to any element: mouseover.delegate="showTip($event, stateAlerts.get('KS'))" – Jereme Mar 17 '16 at 18:15