Skip to content

Instantly share code, notes, and snippets.

@3cp
Created May 30, 2020 03:51
Show Gist options
  • Save 3cp/540da5c0fefe80ce95cd0c33d5f21040 to your computer and use it in GitHub Desktop.
Save 3cp/540da5c0fefe80ce95cd0c33d5f21040 to your computer and use it in GitHub Desktop.
Aurelia demo: binding system
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Dumber Gist</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link
href="https://aurelia.io/styles/aurelia-sandbox.css"
rel="stylesheet"
type="text/css"
/>
<base href="/">
</head>
<!--
Dumber gist uses dumber bundler, the default bundle file
is /dist/entry-bundle.js.
The starting module is pointed to aurelia-bootstrapper
(data-main attribute on script) for Aurelia,
The aurelia bootstrapper then loads up user module "main"
(aurelia-app attribute on <body>) which is your src/main.ts.
-->
<body aurelia-app="main">
<script src="/dist/entry-bundle.js" data-main="aurelia-bootstrapper"></script>
</body>
</html>
{
"dependencies": {
"aurelia-bootstrapper": "^2.3.3"
}
}
.expression-input {
width: 100%;
margin-top: 10px;
margin-bottom: 10px;
}
.expression-well {
background: #fff;
padding: 5px 10px;
border-radius: 4px;
border: 1px solid #70519e;
box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.4),
0 1px 0 0 rgba(255, 255, 255, 0.2);
}
<template>
<require from="./app.css"></require>
<require from="./expression"></require>
<button
repeat.for="example of examples"
click.delegate="expressionString = example.expression"
>
${example.name}
</button>
<h3>Binding Expression</h3>
<input
class="expression-input"
type="text"
value.bind="expressionString"
placeholder="Enter binding expresson...">
<h3>AST</h3>
<div class="expression-well" if.bind="value">
<expression value.bind="value"></expression>
</div>
${error}
</template>
import { inject, observable, Parser } from "aurelia-framework";
@inject(Parser)
export class App {
value = null;
parser;
error;
expression;
@observable expressionString: string;
examples = [
{ name: "Basic Property", expression: `firstName` },
{ name: "Property Path", expression: `person.firstName` },
{ name: "Conditional", expression: `isActive ? 'active' : ''` },
{ name: "Array Index", expression: `myArray[index]` },
{ name: "Binary", expression: `x * y` },
{ name: "Object Literal", expression: `{ x: 3, y: height, z: depth }` },
{ name: "Literal Array", expression: `[a, 1, 'hello', null, undefined]` },
{ name: "Call Method", expression: "save(entity)" },
{ name: "Assignment", expression: "width = rangeInput.value" },
{
name: "Value Converter",
expression: `startDate | dateFormat:'MM/dd/yyyy'`
},
{ name: "Binding Behavior", expression: `lastName & updateTrigger:'blur'` },
{
name: "Kitchen Sink",
expression: `getPosts({ start: minDate, end: maxDate })[0].timestamp | timeAgo & signal:'tick'`
}
];
constructor(parser) {
this.parser = parser;
this.expressionString = this.examples[0].expression;
}
expressionStringChanged(newValue, oldValue) {
this.error = "";
this.expression = null;
try {
this.value = { role: "Root", expression: this.parser.parse(newValue) };
} catch (e) {
this.value = null;
this.error = e.toString();
}
}
}
ul.expression-tree {
padding: 0;
margin: 0;
list-style-type: none;
position: relative;
font-family: sans-serif;
}
ul.expression-tree li {
list-style-type: none;
border-left: 2px solid #808080;
margin-left: 1em;
}
ul.expression-tree li div {
padding-left: 1em;
position: relative;
}
ul.expression-tree li div::before {
content: "";
position: absolute;
top: 0;
left: -2px;
bottom: 50%;
width: 0.75em;
border: 2px solid #808080;
border-top: 0 none transparent;
border-right: 0 none transparent;
}
ul.expression-tree > li:last-child {
border-left: 2px solid transparent;
}
.expression-type {
color: blue;
font-weight: bold;
}
.expression-role {
color: green;
}
.expression-text {
font-family: monospace;
color: #808080;
}
<template>
<require from="./expression.css"></require>
<ul class="expression-tree">
<div>
<span class="expression-type">${type}</span>
<span class="expression-role">[${value.role}]</span>
<code class="expression-text">${value.expression}</code>
</div>
<li repeat.for="child of children">
<expression value.bind="child"></expression>
</li>
</ul>
</template>
import {
AccessThis,
AccessScope,
AccessMember,
AccessKeyed,
Assign,
Binary,
BindingBehavior,
CallFunction,
CallMember,
CallScope,
Conditional,
LiteralPrimitive,
LiteralArray,
LiteralObject,
LiteralString,
Unary,
ValueConverter,
bindable,
containerless
} from "aurelia-framework";
@containerless
export class Expression {
@bindable value;
type = null;
children = null;
valueChanged({ role, expression }) {
if (expression instanceof AccessThis) {
this.type = "AccessThis";
this.children = null;
} else if (expression instanceof AccessScope) {
this.type = "AccessScope";
this.children = null;
} else if (expression instanceof AccessMember) {
this.type = "AccessMember";
this.children = [{ role: "Object", expression: expression.object }];
} else if (expression instanceof AccessKeyed) {
this.type = "AccessKeyed";
this.children = [
{ role: "Object", expression: expression.object },
{ role: "Key", expression: expression.key }
];
} else if (expression instanceof Assign) {
this.type = "Assign";
this.children = [
{ role: "Target", expression: expression.target },
{ role: "Value", expression: expression.value }
];
} else if (expression instanceof Binary) {
this.type = "Binary";
this.children = [
{ role: "Left", expression: expression.left },
{ role: "Right", expression: expression.right }
];
} else if (expression instanceof BindingBehavior) {
this.type = "BindingBehavior";
this.children = [
{ role: "Target", expression: expression.expression },
...expression.args.map(x => ({ role: "Argument", expression: x }))
];
} else if (expression instanceof CallFunction) {
this.type = "CallFunction";
this.children = [
{ role: "Function", expression: expression.func },
...expression.args.map(x => ({ role: "Argument", expression: x }))
];
} else if (expression instanceof CallMember) {
this.type = "CallMember";
this.children = [
{ role: "Object", expression: expression.object },
...expression.args.map(x => ({ role: "Argument", expression: x }))
];
} else if (expression instanceof CallScope) {
this.type = "CallScope";
this.children = expression.args.map(x => ({
role: "Argument",
expression: x
}));
} else if (expression instanceof Conditional) {
this.type = "Conditional";
this.children = [
{ role: "Condition", expression: expression.condition },
{ role: "True-Value", expression: expression.yes },
{ role: "False-Value", expression: expression.no }
];
} else if (expression instanceof LiteralPrimitive) {
this.type = "LiteralPrimitive";
this.children = null;
} else if (expression instanceof LiteralString) {
this.type = "LiteralString";
this.children = null;
} else if (expression instanceof LiteralArray) {
this.type = "LiteralArray";
this.children = expression.elements.map(x => ({
role: "Element",
expression: x
}));
} else if (expression instanceof LiteralObject) {
this.type = "LiteralObject";
this.children = expression.values.map(x => ({
role: "Property Value",
expression: x
}));
} else if (expression instanceof Unary) {
this.type = "Unary";
this.children = [{ role: "Target", expression: expression.expression }];
} else if (expression instanceof ValueConverter) {
this.type = "ValueConverter";
this.children = [
{ role: "Target", expression: expression.allArgs[0] },
...expression.args.map(x => ({ role: "Argument", expression: x }))
];
} else {
this.type = "Unknown";
this.children = null;
}
}
}
import {Aurelia} from 'aurelia-framework';
export function configure(aurelia: Aurelia) {
aurelia.use
.standardConfiguration()
.developmentLogging('info');
aurelia.start().then(() => aurelia.setRoot());
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment