Skip to content

Commit

Permalink
Adds ElementAccessor.prototype.write.
Browse files Browse the repository at this point in the history
This is a parallel API to `ElementAccessor.prototype.read`, and serializes the input value to the underlying element. I also updated the existing demo apps which unnecessarily used `live` to have a slightly more reduced and simplified implementation.
  • Loading branch information
dgp1130 committed Sep 4, 2024
1 parent 2ee0db7 commit 07e5ffc
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 21 deletions.
6 changes: 2 additions & 4 deletions src/demo/hello-world/hello-world.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { defineComponent } from 'hydroactive';
import { live } from 'hydroactive/signal-accessors.js';

/** Says hello to HydroActive on hydration. */
export const HelloWorld = defineComponent('hello-world', (host, comp) => {
const name = live(host.query('span#name').access(), comp, String);
name.set('HydroActive');
export const HelloWorld = defineComponent('hello-world', (host) => {
host.query('span#name').access().write('HydroActive', String);
});

// Declare the component tag name for improved type inference in TypeScript.
Expand Down
6 changes: 2 additions & 4 deletions src/demo/hydration/deferred-comp.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { defineComponent } from 'hydroactive';
import { live } from 'hydroactive/signal-accessors.js';

/** Says hello to HydroActive on hydration. */
export const DeferredComp = defineComponent('deferred-comp', (host, comp) => {
const name = live(host.query('span').access(), comp, String);
name.set('HydroActive');
export const DeferredComp = defineComponent('deferred-comp', (host) => {
host.query('span').access().write('HydroActive', String);
});

declare global {
Expand Down
9 changes: 3 additions & 6 deletions src/demo/shadow/closed-shadow.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { defineComponent } from 'hydroactive';
import { live } from 'hydroactive/signal-accessors.js';

/** Accesses the shadow DOM with `host.shadow`. */
export const ClosedShadow = defineComponent('closed-shadow', (host, comp) => {
export const ClosedShadow = defineComponent('closed-shadow', (host) => {
// Query the shadow DOM under `host.shadow`.
const shadowDiv = live(host.shadow.query('div').access(), comp, String);
shadowDiv.set('I\'m blue,');
host.shadow.query('div').access().write('I\'m blue,', String);

// Query the light DOM under `host`.
const lightDiv = live(host.query('div').access(), comp, String);
lightDiv.set('Da ba dee da ba di...');
host.query('div').access().write('Da ba dee da ba di...', String);
});

declare global {
Expand Down
9 changes: 3 additions & 6 deletions src/demo/shadow/open-shadow.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import { defineComponent } from 'hydroactive';
import { live } from 'hydroactive/signal-accessors.js';

/** Accesses the shadow DOM with `host.shadow`. */
export const OpenShadow = defineComponent('open-shadow', (host, comp) => {
export const OpenShadow = defineComponent('open-shadow', (host) => {
// Query the shadow DOM under `host.shadow`.
const shadowDiv = live(host.shadow.query('div').access(), comp, String);
shadowDiv.set('I\'m red!');
host.shadow.query('div').access().write('I\'m red!', String);

// Query the light DOM under `host`.
const lightDiv = live(host.query('div').access(), comp, String);
lightDiv.set('I\'m not...');
host.query('div').access().write('I\'m not...', String);
});

declare global {
Expand Down
88 changes: 88 additions & 0 deletions src/element-accessor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,94 @@ describe('element-accessor', () => {
});
});

describe('write', () => {
it('serializes the input value with the given serializer and writes it to the text content of the element', () => {
{
const el = ElementAccessor.from(parseHtml(HTMLDivElement,
`<div>Hello, World!</div>`));
el.write('Goodbye, World!', String);
expect(el.element.textContent).toBe('Goodbye, World!');
}

{
const el = ElementAccessor.from(parseHtml(HTMLDivElement,
`<div>12345</div>`));
el.write(54321, Number);
expect(el.element.textContent).toBe('54321');
}

{
const el = ElementAccessor.from(parseHtml(HTMLDivElement,
`<div>true</div>`));
el.write(false, Boolean);
expect(el.element.textContent).toBe('false');
}

{
const el = ElementAccessor.from(parseHtml(HTMLDivElement,
`<div>12345</div>`));
el.write(54321n, BigInt);
expect(el.element.textContent).toBe('54321');
}
});

it('writes with the given custom element serializer', () => {
const serializer: ElementSerializer<{ foo: string }, Element> = {
serializeTo({ foo }: { foo: string }, el: Element): void {
el.textContent = foo;
},

deserializeFrom(): { foo: string } {
throw new Error('Unimplemented');
},
};

const el = ElementAccessor.from(parseHtml(HTMLDivElement,
`<div>Hello, World!</div>`));
el.write({ foo: 'bar' }, serializer);
expect(el.element.textContent).toBe('bar');
});

it('writes with the given custom element serializable', () => {
class User {
public constructor(private name: string) {}
public static [toSerializer](): ElementSerializer<User, Element> {
return {
serializeTo(user: User, element: Element): void {
element.textContent = user.name;
},

deserializeFrom(element: Element): User {
return new User(element.textContent!);
},
};
}
}

const el = ElementAccessor.from(parseHtml(HTMLDivElement,
`<div>Hello</div>`));
el.write(new User('Devel'), User);
expect(el.element.textContent).toBe('Devel');
});

it('throws an error if the serialization process throws', () => {
const err = new Error('Failed to deserialize.');
const serializer: ElementSerializer<string, Element> = {
serializeTo(): void {
throw err;
},

deserializeFrom(): string {
throw new Error('Unimplemented');
}
};

const el = ElementAccessor.from(parseHtml(HTMLDivElement,
`<div>Hello, World!</div>`));
expect(() => el.write('unused', serializer)).toThrow(err);
});
});

describe('listen', () => {
it('listens invokes the given callback when the specified event is triggered', () => {
const el = document.createElement('noop-component');
Expand Down
22 changes: 21 additions & 1 deletion src/element-accessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class ElementAccessor<out El extends Element> implements Queryable<El> {
* Provides the value of the text content on the underlying element.
*
* @param token A "token" which identifiers an {@link ElementSerializer} to
* deserialize the read attribute string. A token is one of:
* deserialize the read string. A token is one of:
* * A primitive serializer - {@link String}, {@link Boolean},
* {@link Number}, {@link BigInt}.
* * An {@link ElementSerializer} object.
Expand All @@ -78,6 +78,26 @@ export class ElementAccessor<out El extends Element> implements Queryable<El> {
return serializer.deserializeFrom(this.element);
}

/**
* Writes the given {@param value}, serialized with the serializer provided by
* {@param token}, to the current element.
*
* @param value The value to be serialized and written to the element.
* @param token A "token" which identifiers an {@link ElementSerializer} to
* serialize the value. A token is one of:
* * A primitive serializer - {@link String}, {@link Boolean},
* {@link Number}, {@link BigInt}.
* * An {@link ElementSerializer} object.
* * A {@link ElementSerializable} object.
*/
public write<Value, Token extends ElementSerializerToken<Value, El>>(
value: Value,
token: Token,
): void {
const serializer = resolveSerializer(token) as ElementSerializer<Value, El>;
serializer.serializeTo(value, this.element);
}

/**
* Creates an event listener for the given event which invokes the provided
* handler callback function. This listener is automatically created and
Expand Down

0 comments on commit 07e5ffc

Please sign in to comment.