Skip to content

Commit

Permalink
feat: update module to use the latest ver. of ovee.ja and add docs (#65)
Browse files Browse the repository at this point in the history
* feat: update module to use the latest ver. of ovee.ja and add docs

* refactor: update typings a little

---------

Co-authored-by: Kamil Kondratowicz <[email protected]>
Co-authored-by: Miłosz Mandowski <[email protected]>
  • Loading branch information
3 people authored Oct 21, 2024
1 parent f1c8b9b commit 5c422be
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 47 deletions.
129 changes: 129 additions & 0 deletions docs/guide/addons/content-loader/module.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# ContentLoader Module

The ContentLoader module is responsible for loading and transitioning content. It uses resolvers to manage different types of content transitions and handles errors during the content loading process.

## Constants

- `DEFAULT_TIMEOUT`: The default timeout value for loading content, set to 20,000 milliseconds (20 seconds).

## Methods

### `async loadPage(url, resolverName, target, pushState): Promise<void>`

Asynchronously loads new content using the specified [resolver](./resolver.md).

- `url: string` - The URL of the page to load.
- `resolverName: string` - The name of the resolver to use for the content transition.
- `target?: Element | null `- The target DOM element for the content. Defaults to `null`.
- `pushState?: boolean` - Indicates whether to push the state to the history. Defaults to `true`.

## How it's works

1. Makes a request to load the content from the specified URL with a timeout.
2. Calls the `contentOut` method on the resolver to manage the transition of the old content.
3. Attempts to fetch and parse the new content.
1. If it occurs error, it calls the `handleError` method and the process ends.
2. If fetch attempt is successfull and the `Resolver` should push the state, it updates the browser's history (calls the `updateHistory` method).
4. Calls the resolver's `updateContent` method to update the DOM with the new content.
5. Finally, calls the `contentIn` method on the resolver to finalize the transition.

## Types

### ContentLoaderOptions

Defines the options that can be passed to the `ContentLoader` module.

- `resolvers: Record<string, ResolverClass>` - Required. A record of resolver classes keyed by a string identifier. These resolvers are responsible for managing different aspects of content transitions.
- `timeout?: number` - Optional. The timeout duration in milliseconds for content loading requests.

### ContentLoaderReturn

The return type of the ContentLoader module, providing a method for loading pages.

`loadPage: (url: string, resolverName: string, target?: Element | null, pushState?: boolean) => Promise<void>` - Function to load content using the specified resolver and handle the content transition.

## Example

::: code-group
```ts [app.ts]
import { createApp } from 'ovee.js';
import OveeBarba from '@ovee.js/barba';

import { ContentLoader } from '@ovee.js/content-loader'; // [!code focus]
import { DefaultResolver } from './DefaultResolver';

const root = document.body;

createApp()
.use('ContentLoader', ContentLoader, { // [!code focus]
timeout: 15000, // [!code focus]
resolvers: { // [!code focus]
'default': DefaultResolver, // [!code focus]
} // [!code focus]
}) // [!code focus]
.run(root);
```

```ts [DefaultResolver.ts]
import { Resolver } from '@ovee.js/content-loader';
import gsap from 'gsap';

export class DefaultResolver extends Resolver {
oldContent: HTMLElement | null = null;
newContent: HTMLElement | null = null;
shoudlPushState = true;

async contentOut() {
this.oldContent = document.querySelector('.content');

await gsap.to(this.oldContent, {
autoAlpha: 0,
});
}

handleError() {
window.alert('Oops, something went wrong');

gsap.set(this.oldContent, {
autoAlpha: 1,
});
}

async updateContent(doc: Document) {
this.newContent = doc.querySelector('.content');

if(!this.newContent) {
return;
}

gsap.set(this.oldContent, {
autoAlpha: 0
});

this.oldContent.insertAfter(this.newContent);
this.oldContent.remove();
}

async contentIn() {
await gsap.to(this.newContent, {
autoAlpha: 1
});
}
}
```

```ts [LoadMoreComponent.ts]
export const LoadMore = defineComponent((element) => {
const contentLoader = useModule('ContentLoader', true);
const nextPageUrl = useDataAttr('next-page-url');

async function loadMore() {
if(!nextPageUrl) {
return;
}

await contentLoader?.loadPage(nextPageUrl, 'default', null, false);
}
})
```
:::
57 changes: 57 additions & 0 deletions docs/guide/addons/content-loader/resolver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Resolver

The Resolver class is responsible for managing content transitions and history updates. It integrates with the `@barba/core` library for history management.

## Props

- `shouldPushState: boolean` - Indicates whether the state should be pushed to the history.

## Methods

### `async contentOut(): Promise<void>`

This asynchronous method is intended to handle the transition of the old content. It's an empty method that can be overridden in subclasses.

### `async updateContent(doc: Document): Promise<void>`

Updates the content based on the provided `Document`. This method is asynchronous and is designed to be overridden in subclasses.

`doc: Document` - The new document content.

### `updateHistory(title: string, url: string): void`

Updates the browser's history and document title.

`title: string` - The new title for the document.
`url: string` - The URL to add to the history.

Default:

```ts
updateHistory(title: string, url: string): void {
document.title = title;
history.add(url, 'barba');
}
```

### `handleError(): void`

Handles errors that may occur during the content update or navigation process. The method is currently a stub and can be implemented in subclasses.

### `async contentIn(): Promise<void>`

This asynchronous method is intended to handle the transition of new content. Like `contentOut`, it's an empty method that can be overridden in subclasses.

## Type Alias

### `ResolverClass`

A TypeScript type alias representing the `Resolver` class.

```ts
export type ResolverClass = typeof Resolver;
```

## Example

[See example](./module.md#example)
2 changes: 1 addition & 1 deletion packages/ovee-content-loader/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
},
"peerDependencies": {
"@barba/core": "^2.9.7",
"ovee.js": "2.0.0 - 2.2.x"
"ovee.js": "^3.0.0-alpha.6"
},
"scripts": {
"lint": "eslint . --fix",
Expand Down
102 changes: 58 additions & 44 deletions packages/ovee-content-loader/src/ContentLoader.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,82 @@
import Barba from '@barba/core';
import { Module } from 'ovee.js';
import barba from '@barba/core';
import { defineModule } from 'ovee.js';

import { ResolverClass } from './Resolver';

const { history, dom, request } = Barba;
export const DEFAULT_TIMEOUT = 20000;

export interface ContentLoaderOptions {
resolvers: Record<string, ResolverClass>;
timeout?: number;
resolvers?: Record<string, ResolverClass>;
}

export class ContentLoader extends Module<ContentLoaderOptions> {
timeout = DEFAULT_TIMEOUT;
resolvers: Record<string, ResolverClass> = {};
export interface ContentLoaderReturn {
loadPage: LoadPage;
}

init(): void {
this.timeout = this.options.timeout ?? DEFAULT_TIMEOUT;
this.resolvers = this.options.resolvers ?? {};
}
type LoadPage = (
url: string,
resolverName: string,
target?: Element | null,
pushState?: boolean
) => Promise<void>;

export const ContentLoader = defineModule<ContentLoaderOptions, ContentLoaderReturn>(
({ app, options }) => {
const timeout = options.timeout ?? DEFAULT_TIMEOUT;
const resolvers: Record<string, ResolverClass> = options.resolvers;

getResolver(name: string): ResolverClass | false {
if (this.resolvers[name] === undefined) {
console.error(`Resolver not registered for key ${name}`);
function getResolver(name: string): ResolverClass | false {
if (resolvers[name] === undefined) {
console.error(`[ovee.js/ContentLoader] Resolver not registered for key ${name}`);

return false;
return false;
}

return resolvers[name];
}

return this.resolvers[name];
}
const loadPage: LoadPage = async (url, resolverName, target = null, pushState = true) => {
const ResolverCtor = getResolver(resolverName);

async loadPage(
url: string,
resolverName: string,
target: Element | null = null,
pushState = true
): Promise<void> {
const ResolverCtor = this.getResolver(resolverName);
if (!ResolverCtor) {
return;
}

if (!ResolverCtor) {
return;
}
const resolver = new ResolverCtor(app, target, url, pushState);
const requestPage = barba.request(url, timeout, (reqUrl, reqErr) => {
console.error(`[ovee.js/ContentLoader] Error while requesting ${reqUrl}`, reqErr);

const resolver = new ResolverCtor(this.$app, target, url, pushState);
const requestPage = request(url, this.timeout, (_url, err) => {
console.error(`[ContentLoader] Error while requesting ${_url}`, err);
return false;
});

return false;
});
await resolver.contentOut();

await resolver.contentOut();
let content: string | null = null;

const content = dom.toDocument(await requestPage);
try {
content = await requestPage;
} catch {
resolver.handleError();
}

if (resolver.pushState && resolver.shouldPushState) {
document.title = content.title;
history.add(url, 'barba');
}
if (!content) {
return;
}

await resolver.updateContent(content);
await resolver.contentIn();
}
const parser = new DOMParser();
const doc = parser.parseFromString(content, 'text/html');

if (resolver.pushState && resolver.shouldPushState) {
resolver.updateHistory(doc.title, url);
}

static getName(): string {
return 'ContentLoader';
await resolver.updateContent(doc);
await resolver.contentIn();
};

return {
loadPage,
};
}
}
);
12 changes: 11 additions & 1 deletion packages/ovee-content-loader/src/Resolver.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import barba from '@barba/core';
import { App } from 'ovee.js';

const { history } = barba;

export class Resolver {
shouldPushState = false;

Expand All @@ -15,8 +18,15 @@ export class Resolver {

async contentOut(): Promise<void> {}

updateHistory(title: string, url: string): void {
document.title = title;
history.add(url, 'barba');
}

handleError(): void {}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async updateContent(content: HTMLDocument): Promise<void> {}
async updateContent(doc: Document): Promise<void> {}
}

export type ResolverClass = typeof Resolver;
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4217,7 +4217,7 @@ __metadata:
ovee.js: "workspace:*"
peerDependencies:
"@barba/core": ^2.9.7
ovee.js: 2.0.0 - 2.2.x
ovee.js: ^3.0.0-alpha.6
languageName: unknown
linkType: soft

Expand Down

0 comments on commit 5c422be

Please sign in to comment.