Last active
June 25, 2016 20:31
-
-
Save niieani/3c6b20af33fc1cfc1a512bf71d1751f4 to your computer and use it in GitHub Desktop.
Aurelia Observer Test + evaluate + connectable-binding change
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<require from="./example"></require> | |
<binding-intercept-example if.bind="show"></binding-intercept-example> | |
</template> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export class App { | |
show = true; | |
// constructor() { | |
// setInterval(() => this.show = !this.show, 2000); | |
// } | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function bindingIntercept(interceptor) { | |
return function interception(definition, propertyName, descriptor) { | |
let viewModelValue = descriptor.initializer && descriptor.initializer(); | |
let observers = new Map(); | |
let observerId = 0; | |
const observableGetter = function() { return viewModelValue; } | |
observableGetter.getObserver = function(targetClass) { | |
const thisObserverId = observerId++; | |
return { | |
// doNotCache: true, | |
expressionHook: function(expression, binding) { | |
if (!expression.__intercepted__) { | |
expression.assign = (scope, value) => { | |
// assignement coming from View | |
if (this.interceptorInstance.next) | |
this.interceptorInstance.next(value); | |
} | |
expression.evaluate = (scope, lookupFunctions) => { | |
return this.lastValue; | |
} | |
expression.__intercepted__ = true; | |
} | |
// workaround: | |
// updates value the first time after the expression is hooked | |
// -- unfortunately the target is first evaluated and then connected | |
// this is called during connect and therefore the initial value sneaks in. | |
binding.updateTarget(binding.sourceExpression.evaluate(binding.source, binding.lookupFunctions)); | |
}, | |
bindings: new Set(), | |
subscribe: function(context, binding) { | |
if (this.bindings.size === 0) { | |
this.lastValue = undefined; | |
this.interceptorInstance = getInterceptor(interceptor, viewModelValue, this); | |
} | |
this.bindings.add(binding); | |
observers.set(thisObserverId, this); | |
}, | |
unsubscribe: function(context, binding) { | |
this.bindings.delete(binding); | |
if (this.bindings.size === 0) { | |
observers.delete(thisObserverId); | |
if (this.interceptorInstance.dispose) { | |
this.interceptorInstance.dispose(); | |
this.interceptorInstance = undefined; | |
} | |
} | |
} | |
} | |
} | |
delete descriptor.writable; | |
delete descriptor.initializer; | |
Object.defineProperty(descriptor, 'set', { | |
value: function (newViewModelValue) { | |
// assignement coming from ViewModel: | |
viewModelValue = newViewModelValue; | |
updateObservers(interceptor, viewModelValue, observers); | |
} | |
}); | |
Object.defineProperty(descriptor, 'get', { | |
value: observableGetter | |
}); | |
} | |
} | |
function getBindingValueSetter(observer) { | |
return function (value) { | |
if (observer.lastValue !== value) { | |
observer.lastValue = value; | |
if (observer.interceptorInstance) { | |
for (let binding of observer.bindings) { | |
binding.call('Binding:source'); | |
} | |
} | |
} | |
} | |
} | |
function updateObservers(interceptor, viewModelValue, observers) { | |
for (let observer of observers.values()) { | |
if (observer.interceptorInstance.dispose) { | |
observer.interceptorInstance.dispose(); | |
} | |
observer.interceptorInstance = getInterceptor(interceptor, viewModelValue, observer); | |
for (let binding of observer.bindings) { | |
binding.call('Binding:source'); | |
} | |
} | |
} | |
function getInterceptor(interceptor, viewModelValue, observer) { | |
return interceptor(viewModelValue, getBindingValueSetter(observer)) || {}; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as aureliaBinding from 'aurelia-binding'; | |
export const sourceContext = 'Binding:source'; | |
const slotNames = []; | |
const versionSlotNames = []; | |
for (let i = 0; i < 100; i++) { | |
slotNames.push(`_observer${i}`); | |
versionSlotNames.push(`_observerVersion${i}`); | |
} | |
function addObserver(observer, expression) { | |
// find the observer. | |
let observerSlots = this._observerSlots === undefined ? 0 : this._observerSlots; | |
let i = observerSlots; | |
while (i-- && this[slotNames[i]] !== observer) { | |
// Do nothing | |
} | |
// if we are not already observing, put the observer in an open slot and subscribe. | |
if (i === -1) { | |
i = 0; | |
while (this[slotNames[i]]) { | |
i++; | |
} | |
this[slotNames[i]] = observer; | |
observer.subscribe(sourceContext, this); | |
// increment the slot count. | |
if (i === observerSlots) { | |
this._observerSlots = i + 1; | |
} | |
} | |
// monkey patching /start | |
if (observer && observer.expressionHook) { | |
observer.expressionHook(expression, this); | |
} | |
// monkey patching /end | |
// set the "version" when the observer was used. | |
if (this._version === undefined) { | |
this._version = 0; | |
} | |
this[versionSlotNames[i]] = this._version; | |
} | |
function observeProperty(obj, propertyName, expression) { | |
let observer = this.observerLocator.getObserver(obj, propertyName); | |
addObserver.call(this, observer, expression); | |
} | |
aureliaBinding.AccessScope.prototype.connect = function connect(binding, scope) { | |
let context = aureliaBinding.getContextFor(this.name, scope, this.ancestor); | |
// monkey patching observeProperty | |
binding.observeProperty = observeProperty; | |
binding.observeProperty(context, this.name, this); | |
} | |
aureliaBinding.AccessMember.prototype.connect = function connect(binding, scope) { | |
this.object.connect(binding, scope); | |
let obj = this.object.evaluate(scope); | |
if (obj) { | |
// monkey patching observeProperty | |
binding.observeProperty = observeProperty; | |
binding.observeProperty(obj, this.name, this); | |
} | |
} | |
aureliaBinding.AccessMember.prototype.connect = function connect(binding, scope) { | |
this.object.connect(binding, scope); | |
let obj = this.object.evaluate(scope); | |
if (obj) { | |
// monkey patching observeProperty | |
binding.observeProperty = observeProperty; | |
binding.observeProperty(obj, this.name, this); | |
} | |
} | |
aureliaBinding.AccessKeyed.prototype.connect = function connect(binding, scope) { | |
this.object.connect(binding, scope); | |
let obj = this.object.evaluate(scope); | |
if (obj instanceof Object) { | |
this.key.connect(binding, scope); | |
let key = this.key.evaluate(scope); | |
// observe the property represented by the key as long as it's not an array | |
// being accessed by an integer key which would require dirty-checking. | |
if (key !== null && key !== undefined | |
&& !(Array.isArray(obj) && typeof(key) === 'number')) { | |
// monkey patching observeProperty | |
binding.observeProperty = observeProperty; | |
binding.observeProperty(obj, key, this); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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(['./connectable-binding', 'aurelia-bootstrapper']); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment