Skip to content

Instantly share code, notes, and snippets.

@niieani
Last active January 4, 2018 02:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save niieani/e4185ddcdb80b67dbcbcd209689c6901 to your computer and use it in GitHub Desktop.
Save niieani/e4185ddcdb80b67dbcbcd209689c6901 to your computer and use it in GitHub Desktop.
Aurelia Observer Test + AST hooks
<template>
<require from="./example"></require>
<binding-intercept-example if.bind="show"></binding-intercept-example>
</template>
export class App {
show = true;
// constructor() {
// setInterval(() => this.show = !this.show, 4000);
// }
}
import * as aureliaBinding from 'aurelia-binding';
aureliaBinding.AccessScope.prototype.evaluate = function evaluate(scope, lookupFunctions) {
let context = aureliaBinding.getContextFor(this.name, scope, this.ancestor);
return getScoped(context, this.name);
}
aureliaBinding.AccessScope.prototype.assign = function assign(scope, value) {
let context = aureliaBinding.getContextFor(this.name, scope, this.ancestor);
return context ? (setScoped(context, this.name, value)) : undefined;
}
aureliaBinding.AccessMember.prototype.evaluate = function evaluate(scope, lookupFunctions) {
let instance = this.object.evaluate(scope, lookupFunctions);
return instance === null || instance === undefined ? instance : getScoped(instance, this.name);
}
aureliaBinding.AccessMember.prototype.assign = function assign(scope, value) {
let instance = this.object.evaluate(scope);
if (instance === null || instance === undefined) {
instance = {};
this.object.assign(scope, instance);
}
return setScoped(instance, this.name, value); // eslint-disable-line
}
aureliaBinding.AccessKeyed.prototype.evaluate = function evaluate(scope, lookupFunctions) {
let instance = this.object.evaluate(scope, lookupFunctions);
let lookup = this.key.evaluate(scope, lookupFunctions);
return getKeyed(instance, lookup);
}
aureliaBinding.AccessKeyed.prototype.assign = function assign(scope, value) {
let instance = this.object.evaluate(scope);
let lookup = this.key.evaluate(scope);
return setKeyed(instance, lookup, value);
}
function getScoped(obj, key) {
let descriptor = Object.getPropertyDescriptor(obj, key);
if (descriptor && descriptor.get && descriptor.get.bindingGetter) {
return descriptor.get.bindingGetter();
} else {
return obj[key];
}
}
function setScoped(obj, key, value) {
let descriptor = Object.getPropertyDescriptor(obj, key);
if (descriptor && descriptor.set && descriptor.set.bindingSetter) {
descriptor.set.bindingSetter(value);
} else {
return obj[key] = value;
}
return value;
}
function getKeyed(obj, key) {
if (Array.isArray(obj)) {
return obj[parseInt(key, 10)];
} else if (obj) {
return getScoped(obj, key);
} else if (obj === null || obj === undefined) {
return undefined;
}
return obj[key];
}
function setKeyed(obj, key, value) {
if (Array.isArray(obj)) {
let index = parseInt(key, 10);
if (obj.length <= index) {
obj.length = index + 1;
}
obj[index] = value;
} else {
setScoped(obj, key, value);
}
return value;
}
export function bindingIntercept(interceptor) {
return function interception(definition, propertyName, descriptor) {
let viewModelValue = descriptor.initializer && descriptor.initializer();
let bindings = new Set();
let viewValue;
let interceptorInstance;
function getBindingValueSetter() {
return function (value) {
if (viewValue === value) {
return;
}
viewValue = value;
if (interceptorInstance) {
for (let binding of bindings) {
binding.call('Binding:source');
}
}
}
}
function getInterceptor() {
return interceptor(viewModelValue, getBindingValueSetter()) || {};
}
function restartObservers() {
if (bindings.size === 0) {
return
}
if (interceptorInstance && interceptorInstance.dispose) {
interceptorInstance.dispose();
}
interceptorInstance = getInterceptor();
for (let binding of bindings) {
binding.call('Binding:source');
}
}
const observableGetter = function() {
return viewModelValue;
}
const observableSetter = function(newViewModelValue) {
// assignement coming from ViewModel:
viewModelValue = newViewModelValue;
restartObservers();
}
observableGetter.bindingGetter = function() {
// view requesting value
return viewValue;
}
observableSetter.bindingSetter = function(value) {
// assignement coming from View
if (interceptorInstance && interceptorInstance.next) {
interceptorInstance.next(value);
}
}
observableGetter.getObserver = function(targetClass) {
return {
// doNotCache: true,
subscribe: function(context, binding) {
bindings.add(binding);
if (bindings.size === 1) {
interceptorInstance = getInterceptor();
binding.updateTarget(binding.sourceExpression.evaluate(binding.source, binding.lookupFunctions));
}
},
unsubscribe: function(context, binding) {
bindings.delete(binding);
if (bindings.size === 0) {
viewValue = undefined;
if (interceptorInstance.dispose) {
interceptorInstance.dispose();
interceptorInstance = undefined;
}
}
}
}
}
delete descriptor.writable;
delete descriptor.initializer;
Object.defineProperty(descriptor, 'set', {
value: observableSetter
});
Object.defineProperty(descriptor, 'get', {
value: observableGetter
});
}
}
<template>
<p>Promise Binding: ${somePromise ? somePromise : 'loading... (TENARY)'}</p>
<p>Promise Binding: ${somePromise + ' post-concat'}</p>
<p>Promise Binding: ${'pre-concat ' + somePromise + ' post-concat'}</p>
<p>Promise Binding: ${somePromise || 'loading (OR ||)...'}</p>
<button click.delegate="changePromise()">New Promise</button>
<br/><br/>
<p>Obj Promise Binding: ${someObjPromise.value || 'loading...'}</p>
<br/><br/>
<p>Timer Binding: ${someTimer}</p>
<br/><br/>
Two Way Binding (see console):<br/>
<input value.bind="firstName">
<button click.delegate="changeFirstName()">Change Name to Bazyli</button>
</template>
import {bindingIntercept} from './binding-intercept';
export class BindingInterceptExample {
@bindingIntercept(promiseBinding)
somePromise = new Promise(resolve => setTimeout(() => resolve('Hello!'), 2000));
changePromise() {
this.somePromise = new Promise(resolve => setTimeout(() => resolve('Awesome!'), 2000));
}
@bindingIntercept(promiseBinding)
someObjPromise = new Promise(resolve => setTimeout(() => resolve({value: 'accessed promise.value!'}), 2000));
@bindingIntercept(timer) someTimer;
@bindingIntercept(twoWayIntercept) firstName = 'Rob';
changeFirstName() {
this.firstName = 'Bazyli';
}
}
function promiseBinding(promise, bindingValueSetter) {
bindingValueSetter(undefined); // initial value;
promise.then(value => bindingValueSetter(value));
}
function timer(any, bindingValueSetter) {
let i = 0;
let timer = setInterval(() => bindingValueSetter(i++), 1000);
return {
dispose: () => clearInterval(timer)
}
}
function twoWayIntercept(value, bindingValueSetter) {
console.log('ViewModel changed value to:', value);
bindingValueSetter(value); // initial value;
return {
next: (value) => {
console.log('View changed value to:', value)
}
}
}
<!doctype html>
<html>
<head>
<title>Aurelia</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body aurelia-app>
<h1>Loading...</h1>
<script src="https://jdanyow.github.io/rjs-bundle/node_modules/requirejs/require.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/config.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/aurelia.js"></script>
<script src="https://jdanyow.github.io/rjs-bundle/bundles/babel.js"></script>
<script>
require(['./ast-hooks', 'aurelia-bootstrapper']);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment