2

I'm building a drag-and-drop workspace, similar to the ones you find for making mockups. I have a workspace custom element that has a larger, nested element that can be zoomed and panned. Therefore, I need to carefully track size and position data of the workspace and all contained elements.

In my attached event of my custom element, I programatically set the height and width of the workspace as a JavaScript object, which is bound to the css in the view:

workspaceCustomElement.js

export class WorkspaceCustomElement {

    constructor(element) {
        this.element = element;
    }

    attached() {
        this.workspace.size = {
            height: this.element.height * 5,
            width: this.element.width * 5
        }
    }
}

workspaceCustomElement.html

<div css="height: ${workspace.height}px; width: ${workspace.width}px; 
    left: ${(element.clientWidth - workspace.width)/2}px; 
    top: ${(element.clientHeight - workspace.height)/2}px;"></div>

Now I'm running into problems trying to grab the positions of my child elements. I have attached callbacks on them as well, but they are being evaluated before the attached callback above, and so the css binding hasn't been evaluated, and the size and positions are wrong.

I need to add a callback after the attached() has been evaluated and the bindings have been updated. I can accomplish this by using a setTimeout hack, but I have no confidence that this will always work.

attached() {
    this.workspace.size = {
        height: this.element.height * 5,
        width: this.element.width * 5
    }

    setTimeout(() => {
        let components = this.element.querySelectorAll('.component');
        Array.prototype.forEach.call(components, (component) => {
            let model = component.model;
            model.position = { 
                x: component.clientLeft,
                y: component.clientTop
            };
        }
    }, 0)
}

Is there a better, more reliable way to queue an instruction after the next binding update?

Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
Matthew James Davis
  • 12,134
  • 7
  • 61
  • 90
  • 2
    How about using Aurelia's `TaskQueue`? http://stackoverflow.com/questions/36049391/is-there-a-callback-or-promise-for-aurelia-show-bind – Miroslav Popovic Jul 14 '16 at 22:57

2 Answers2

5

The best practices is to add a task to the TaskQueue. Under the hood, the binding engine uses the TaskQueue itself, so adding a new task will queue it behind the binding updates.

workspaceCustomElement.js

export class WorkspaceCustomElement {

    constructor(element, queue) {
        this.element = element;
        this.queue = queue;
    }

    attached() {
        this.workspace.size = {
            height: this.element.height * 5,
            width: this.element.width * 5
        }
        this.queue.queueMicroTask(() => {
            let components = this.element.querySelectorAll('.component');
            Array.prototype.forEach.call(components, (component) => {
                let model = component.model;
                model.position = { 
                    x: component.clientLeft,
                    y: component.clientTop
                };
            }
        });
    }
}

See here for more information: TaskQueue API

Matthew James Davis
  • 12,134
  • 7
  • 61
  • 90
3

you could create a binding behavior that invokes a callback of your choosing whenever the binding updates the target (the DOM element/custom-element/custom-attribute).

Here's an example: https://gist.run?id=28c0fedb21b5d8100913d3bc5500499f

target-updated.js

export class TargetUpdatedBindingBehavior {
  bind(binding, source, callback) {
    // override the binding's updateTarget method.  add logic to invoke
    // the callback passed as a parameter to the binding behavior.
    binding.standardUpdateTarget = binding.updateTarget;
    binding.targetUpdatedCallback = callback;
    binding.updateTarget = function(value) {
      this.standardUpdateTarget(value);
      this.targetUpdatedCallback(this.target, this.targetProperty, value);
    };
  }

  unbind(binding, source) {
    // revert the binding to it's original state.
    binding.updateTarget = binding.standardUpdateTarget;
    binding.standardUpdateTarget = null;
    binding.targetUpdatedCallback = null;
  }
}

http://aurelia.io/hub.html#/doc/article/aurelia/binding/latest/binding-binding-behaviors

@MathewJamesDavis's answer is perfectly valid, I'm just offering this as a second option.

Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
  • wow cool, in my case, wouldn't this be a tricky implementation since I'm actually triggering an event on an array of elements? – Matthew James Davis Jul 15 '16 at 14:50
  • This is genious, I'm using this technique in many places. Basically everywhere, when I don't want direct binding, just being triggered if something changed. – balazska Mar 13 '17 at 12:52