Skip to content

Commit 1fc764c

Browse files
authored
Add parent context access to declarative HTML bindings (#7103)
# Pull Request ## πŸ“– Description This change adds the ability to refer to the parent context in declarative HTML bindings. ## βœ… Checklist ### General <!--- Review the list and put an x in the boxes that apply. --> - [x] I have included a change request file using `$ npm run change` - [x] I have added tests for my changes. - [x] I have tested my changes. - [x] I have updated the project documentation to reflect my changes. - [x] I have read the [CONTRIBUTING](https://github.com/microsoft/fast/blob/main/CONTRIBUTING.md) documentation and followed the [standards](https://github.com/microsoft/fast/blob/main/CODE_OF_CONDUCT.md#our-standards) for this project.
1 parent 80be60f commit 1fc764c

File tree

10 files changed

+88
-47
lines changed

10 files changed

+88
-47
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Add parent context access to bindings",
4+
"packageName": "@microsoft/fast-html",
5+
"email": "[email protected]",
6+
"dependentChangeType": "none"
7+
}

β€Žpackages/web-components/fast-html/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ Where the right operand can be either a reference to a value (string e.g. `{{foo
117117
<ul><f-repeat value="{{item in list}}"><li>{{item}}</li></f-repeat></ul>
118118
```
119119

120+
Should you need to refer to the parent element (not the individual item in the list), you can use `../`. This will map to what `html` tag template literal uses, `c.parent`.
121+
122+
Example:
123+
```html
124+
<ul><f-repeat value="{{item in list}}"><li>{{item}} - {{../title}}</li></f-repeat></ul>
125+
```
126+
120127
- **partial & apply**
121128

122129
These directives are new to the declarative HTML model and allow for the ability to declare a `partial` directive containing a template partial which can then be referenced by an `apply` directive.

β€Žpackages/web-components/fast-html/src/components/template.ts

Lines changed: 36 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -125,68 +125,70 @@ class TemplateElement extends FASTElement {
125125
const { operator, left, right, rightIsValue } = getOperator(
126126
behaviorConfig.value
127127
);
128-
let whenLogic = (x: boolean) => pathResolver(left, self)(x);
128+
let whenLogic = (x: boolean, c: any) =>
129+
pathResolver(left, self)(x, c);
129130

130131
switch (operator) {
131132
case "!":
132-
whenLogic = (x: boolean) => !pathResolver(left, self)(x);
133+
whenLogic = (x: boolean, c: any) =>
134+
!pathResolver(left, self)(x, c);
133135
break;
134136
case "==":
135-
whenLogic = (x: boolean) =>
136-
pathResolver(left, self)(x) ==
137+
whenLogic = (x: boolean, c: any) =>
138+
pathResolver(left, self)(x, c) ==
137139
(rightIsValue
138140
? right
139-
: pathResolver(right as string, self)(x));
141+
: pathResolver(right as string, self)(x, c));
140142
break;
141143
case "!=":
142-
whenLogic = (x: boolean) =>
143-
pathResolver(left, self)(x) !=
144+
whenLogic = (x: boolean, c: any) =>
145+
pathResolver(left, self)(x, c) !=
144146
(rightIsValue
145147
? right
146-
: pathResolver(right as string, self)(x));
148+
: pathResolver(right as string, self)(x, c));
147149
break;
148150
case "&&":
149151
case "&amp;&amp;":
150-
whenLogic = (x: boolean) =>
151-
pathResolver(left, self)(x) &&
152+
whenLogic = (x: boolean, c: any) =>
153+
pathResolver(left, self)(x, c) &&
152154
(rightIsValue
153155
? right
154-
: pathResolver(right as string, self)(x));
156+
: pathResolver(right as string, self)(x, c));
155157
break;
156158
case "||":
157-
whenLogic = (x: boolean) =>
158-
pathResolver(left, self)(x) ||
159+
whenLogic = (x: boolean, c: any) =>
160+
pathResolver(left, self)(x, c) ||
159161
(rightIsValue
160162
? right
161-
: pathResolver(right as string, self)(x));
163+
: pathResolver(right as string, self)(x, c));
162164
break;
163165
case ">=":
164-
whenLogic = (x: boolean) =>
165-
pathResolver(left, self)(x) >=
166+
whenLogic = (x: boolean, c: any) =>
167+
pathResolver(left, self)(x, c) >=
166168
(rightIsValue
167169
? right
168-
: pathResolver(right as string, self)(x));
170+
: pathResolver(right as string, self)(x, c));
169171
break;
170172
case ">":
171-
whenLogic = (x: boolean) =>
172-
pathResolver(left, self)(x) >
173+
whenLogic = (x: boolean, c: any) =>
174+
pathResolver(left, self)(x, c) >
173175
(rightIsValue
174176
? right
175-
: pathResolver(right as string, self)(x));
177+
: pathResolver(right as string, self)(x, c));
176178
break;
177179
case "<=":
178-
whenLogic = (x: boolean) =>
179-
pathResolver(left, self)(x) <=
180+
whenLogic = (x: boolean, c: any) =>
181+
pathResolver(left, self)(x, c) <=
180182
(rightIsValue
181183
? right
182-
: pathResolver(right as string, self)(x));
184+
: pathResolver(right as string, self)(x, c));
183185
break;
184186
case "<":
185-
whenLogic = (x: boolean) =>
186-
pathResolver(left, self)(x) <
187+
whenLogic = (x: boolean, c: any) =>
188+
pathResolver(left, self)(x, c) <
187189
(rightIsValue
188190
? right
189-
: pathResolver(right as string, self)(x));
191+
: pathResolver(right as string, self)(x, c));
190192
break;
191193
}
192194

@@ -211,7 +213,7 @@ class TemplateElement extends FASTElement {
211213

212214
externalValues.push(
213215
repeat(
214-
x => pathResolver(valueAttr[2], self)(x),
216+
(x, c) => pathResolver(valueAttr[2], self)(x, c),
215217
this.resolveTemplateOrBehavior(strings, values)
216218
)
217219
);
@@ -235,7 +237,7 @@ class TemplateElement extends FASTElement {
235237

236238
externalValues.push(
237239
when(
238-
x => pathResolver(behaviorConfig.value, self)(x),
240+
(x, c) => pathResolver(behaviorConfig.value, self)(x, c),
239241
() => this.partials[partial]
240242
)
241243
);
@@ -306,7 +308,8 @@ class TemplateElement extends FASTElement {
306308
behaviorConfig.openingEndIndex,
307309
behaviorConfig.closingStartIndex
308310
);
309-
const binding = (x: any) => pathResolver(propName, self)(x);
311+
const binding = (x: any, c: any) =>
312+
pathResolver(propName, self)(x, c);
310313
values.push(binding);
311314
await this.resolveInnerHTML(
312315
innerHTML.slice(behaviorConfig.closingEndIndex, innerHTML.length),
@@ -323,14 +326,16 @@ class TemplateElement extends FASTElement {
323326
behaviorConfig.openingEndIndex,
324327
behaviorConfig.closingStartIndex - 2
325328
);
326-
const binding = (x: any) => pathResolver(propName, self)(x)();
329+
const binding = (x: any, c: any) =>
330+
pathResolver(propName, self)(x, c)();
327331
values.push(binding);
328332
} else {
329333
const propName = innerHTML.slice(
330334
behaviorConfig.openingEndIndex,
331335
behaviorConfig.closingStartIndex
332336
);
333-
const binding = (x: any) => pathResolver(propName, self)(x);
337+
const binding = (x: any, c: any) =>
338+
pathResolver(propName, self)(x, c);
334339
values.push(binding);
335340
}
336341

β€Žpackages/web-components/fast-html/src/components/utilities.spec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,16 +188,19 @@ test.describe("utilities", async () => {
188188

189189
test.describe("pathResolver", async () => {
190190
test("should resolve a path with no nesting", async () => {
191-
expect(pathResolver("foo")({ foo: "bar" })).toEqual("bar");
191+
expect(pathResolver("foo")({ foo: "bar" }, {})).toEqual("bar");
192192
});
193193
test("should resolve a path with nesting", async () => {
194-
expect(pathResolver("foo.bar.bat")({ foo: { bar: { bat: "baz" }} })).toEqual("baz");
194+
expect(pathResolver("foo.bar.bat")({ foo: { bar: { bat: "baz" }} }, {})).toEqual("baz");
195195
});
196196
test("should resolve a path with no nesting and self reference", async () => {
197-
expect(pathResolver("foo", true)("bar")).toEqual("bar");
197+
expect(pathResolver("foo", true)("bar", {})).toEqual("bar");
198198
});
199199
test("should resolve a path with nesting and self reference", async () => {
200-
expect(pathResolver("foo.bar.bat", true)({ bar: { bat: "baz" }})).toEqual("baz");
200+
expect(pathResolver("foo.bar.bat", true)({ bar: { bat: "baz" }}, {})).toEqual("baz");
201+
});
202+
test("should resolve a path with context", async () => {
203+
expect(pathResolver("../foo")({}, {parent: {foo: "bar"}})).toEqual("bar");
201204
});
202205
});
203206
});

β€Žpackages/web-components/fast-html/src/components/utilities.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -346,10 +346,11 @@ type AccessibleObject = { [key: string]: AccessibleObject };
346346
export function pathResolver(
347347
path: string,
348348
self: boolean = false
349-
): (accessibleObject: any) => any {
349+
): (accessibleObject: any, context: any) => any {
350350
let splitPath = path.split(".");
351+
const usesContext = path.startsWith("../");
351352

352-
if (self) {
353+
if (self && !usesContext) {
353354
if (splitPath.length > 1) {
354355
splitPath = splitPath.slice(1);
355356
} else {
@@ -359,13 +360,28 @@ export function pathResolver(
359360
}
360361
}
361362

362-
if (splitPath.length === 1) {
363+
if (splitPath.length === 1 && !usesContext) {
363364
return (accessibleObject: AccessibleObject) => {
364365
return accessibleObject?.[splitPath[0]];
365366
};
366367
}
367368

368-
return (accessibleObject: AccessibleObject) => {
369+
return (accessibleObject: AccessibleObject, context: AccessibleObject) => {
370+
if (usesContext) {
371+
splitPath = [];
372+
path.split("../").forEach(pathItem => {
373+
if (pathItem === "") {
374+
splitPath.unshift("parent");
375+
} else {
376+
splitPath.push(...pathItem.split("."));
377+
}
378+
});
379+
380+
return splitPath.reduce((previousAccessors, pathItem) => {
381+
return previousAccessors?.[pathItem];
382+
}, context);
383+
}
384+
369385
return splitPath.reduce((previousAccessors, pathItem) => {
370386
return previousAccessors?.[pathItem];
371387
}, accessibleObject);

β€Žpackages/web-components/fast-html/src/fixtures/repeat/main.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { FASTElement, observable } from "@microsoft/fast-element";
44
class TestElement extends FASTElement {
55
@observable
66
list: Array<string> = ["Foo", "Bar"];
7+
8+
parent: string = "Bat";
79
}
810
TestElement.define({
911
name: "test-element",

β€Žpackages/web-components/fast-html/src/fixtures/repeat/repeat.fixture.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
</head>
88
<body>
99
<f-template name="test-element">
10-
<template><ul><f-repeat value="{{item in list}}"><li>{{item}}</li></f-repeat></ul></template>
10+
<template><ul><f-repeat value="{{item in list}}"><li>{{item}} - {{../parent}}</li></f-repeat></ul></template>
1111
</f-template>
1212
<test-element>
13-
<template shadowrootmode="open"><ul><li>Foo</li><li>Bar</li></ul></template>
13+
<template shadowrootmode="open"><ul><li>Foo - Bat</li><li>Bar - Bat</li></ul></template>
1414
</test-element>
1515
</body>
1616
</html>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<ul><f-repeat value="{{item in list}}"><li>{{item}}</li></f-repeat></ul>
1+
<ul><f-repeat value="{{item in list}}"><li>{{item}} - {{../parent}}</li></f-repeat></ul>

β€Žpackages/web-components/fast-html/src/fixtures/repeat/repeat.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"parent": "Bat",
23
"list": [
34
"Foo",
45
"Bar"

β€Žpackages/web-components/fast-html/src/fixtures/repeat/repeat.spec.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ test.describe("f-template", async () => {
88
let customElementListItems = await customElement.locator("li");
99

1010
expect(await customElementListItems.count()).toEqual(2);
11-
expect(await customElementListItems.nth(0).textContent()).toEqual("Foo");
12-
expect(await customElementListItems.nth(1).textContent()).toEqual("Bar");
11+
expect(await customElementListItems.nth(0).textContent()).toEqual("Foo - Bat");
12+
expect(await customElementListItems.nth(1).textContent()).toEqual("Bar - Bat");
1313

1414
await page.evaluate(() => {
1515
const customElement = document.getElementsByTagName("test-element");
@@ -25,8 +25,8 @@ test.describe("f-template", async () => {
2525

2626
customElementListItems = await customElement.locator("li");
2727
expect(await customElementListItems.count()).toEqual(3);
28-
expect(await customElementListItems.nth(0).textContent()).toEqual("A");
29-
expect(await customElementListItems.nth(1).textContent()).toEqual("B");
30-
expect(await customElementListItems.nth(2).textContent()).toEqual("C");
28+
expect(await customElementListItems.nth(0).textContent()).toEqual("A - Bat");
29+
expect(await customElementListItems.nth(1).textContent()).toEqual("B - Bat");
30+
expect(await customElementListItems.nth(2).textContent()).toEqual("C - Bat");
3131
});
3232
});

0 commit comments

Comments
Β (0)