Skip to content

Instantly share code, notes, and snippets.

@peisenmann
Last active July 9, 2017 03:05
Show Gist options
  • Save peisenmann/c5179e5daf62e45c4a55 to your computer and use it in GitHub Desktop.
Save peisenmann/c5179e5daf62e45c4a55 to your computer and use it in GitHub Desktop.
Aurelia custom attribute to invoke a callback when an element gets added to the dom. Demo: http://plnkr.co/edit/fguuz4
import {customAttribute, bindable, LogManager} from 'aurelia-framework';
const logger:Logger = LogManager.getLogger("attached");
/**
* MIT License. Patrick Eisenmann.
* Apply this attribute to an element to have a callback invoked when the element added to the DOM
*
* @param {Function} callback. Required. The function to be invoked.
* The first argument to the callback is the Element that had the attached on it.
* Any arguments specified by the args parameter will be passed as individual args after the element.
* @param {Object|ViewModel} scope. Optional. The thisArg of the callback when invoked.
* @param {Array} args. Optional. Additional arguments passed to the callback.
*
* Example:
*
* foo-view.js
export class FooView {
constructor() {
this.$this = this;
this.elements = {};
this.data = [{
id: 10000,
name: 'Hello'
},{
id: 10001,
name: 'World'
},{
id: 10002,
name: 'Tomato'
}];
}
mySubElementAttached(element, index) {
$(element).someJQueryPlugin();
this.elements[index] = element;
}
}
* foo-view.html
<template>
<require from="attributes/attached"></require>
<div repeat.for="datum of data" attached="callback.bind: $parent.mySubElementAttached; scope.bind: $parent.$this; args.bind: [$index]">
${$index}: ${datum.id}: ${datum.name}
</div>
</template>
*/
@customAttribute('attached')
export class Attached {
@bindable callback;
@bindable scope;
@bindable args;
static inject = [Element];
constructor(element) {
this.element = element;
}
attached() {
logger.info("Attached!", this.args);
if (typeof this.callback === 'function') {
this.callback.apply(this.scope, [this.element, ...(this.args || [])]);
}
else {
logger.error('Callback was type', typeof this.callback, ', not a function. Did you forget to callback.bind? Callback: ', this.callback, ' Scope: ', this.scope, 'Args: ', this.args);
throw `attached attribute invoked, but callback was '${this.callback}' instead of a function.`;
}
}
}
@PieterHartzer
Copy link

I used this to achieve what I wanted in my own attribute, thank you for this code. The way you handled the scope was not ideal for me and after a lot of struggling I found a way around it. If you have the following code:
bind(bindingContext) { this.bindingContext = bindingContext; }
then you can replace this.callback.apply(this.scope, [this.element, ...(this.args || [])]); with this.callback.apply(this.bindingContext, [this.element, ...(this.args || [])]); and you would not have to rely on scope.bind: $parent.$this; in the view.

I have not tested it on your code but I presume it will work since it worked on mine that was copied from yours.

@gowravshekar
Copy link

A new context is created when repeat.for is used. bindingContext will contain the child context and not the parent context. Under such conditions, you need to pass scope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment