Skip to content

Commit

Permalink
improve ajax web components - add fetch URL data attribute
Browse files Browse the repository at this point in the history
  • Loading branch information
basher committed Nov 15, 2024
1 parent 95fb621 commit 4fc284a
Show file tree
Hide file tree
Showing 9 changed files with 29 additions and 18 deletions.
2 changes: 1 addition & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "web-ui-boilerplate",
"description": "UI boilerplate for websites/webapps using vanilla HTML/CSS/JavaScript, powered by Storybook, bundled by Parcel.",
"author": "basher",
"version": "4.0.4",
"version": "4.0.5",
"license": "ISC",
"repository": {
"type": "git",
Expand Down
8 changes: 5 additions & 3 deletions ui/src/javascript/utils/ajax-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,18 @@ export const ajaxErrorHandler = (arg: AjaxError): void => {
* @param {HTMLElement} ajaxTrigger - element that triggers the Ajax request
* @param {string} eventType - event type that triggers the Ajax request
* @param {Function} ajaxCallback - callback function that handles the Ajax request
* * @param {string} ajaxUrl - URL of data to be fetched (e.g. HTML fragment or API endpoint)
* @returns {void}
*/
interface AjaxEvent {
ajaxTrigger: HTMLElement;
eventType: string;
ajaxCallback: (ajaxContainer: HTMLElement) => void;
ajaxCallback: (ajaxContainer: HTMLElement, ajaxUrl: string) => void;
ajaxUrl: string;
}

export const ajaxEventHandler = (arg: AjaxEvent): void => {
const { ajaxTrigger, eventType, ajaxCallback } = arg;
const { ajaxTrigger, eventType, ajaxCallback, ajaxUrl } = arg;

// Value of 'data-fetch-trigger' must match 'data-fetch-container' so that an Ajax trigger (e.g. button) loads content into the correct container.
const target = ajaxTrigger?.dataset.fetchTarget;
Expand All @@ -88,7 +90,7 @@ export const ajaxEventHandler = (arg: AjaxEvent): void => {
ajaxTrigger.addEventListener(eventType, () => {
if (ajaxContainer) {
ajaxContainer.classList.remove('ajax__error');
ajaxCallback(ajaxContainer);
ajaxCallback(ajaxContainer, ajaxUrl);
}
});
};
10 changes: 6 additions & 4 deletions ui/src/javascript/web-components/webui-fetch-html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,28 @@ import {

export default class WebUIFetchHtml extends HTMLElement {
private fetchTrigger: HTMLButtonElement | null;
private fetchUrl: string | undefined;

constructor() {
super();

this.fetchTrigger = this.querySelector('[data-fetch-target]');
this.fetchUrl = this.fetchTrigger?.dataset.fetchUrl;

if (!this.fetchTrigger) return;
if (!this.fetchTrigger || !this.fetchUrl) return;

ajaxEventHandler({
ajaxTrigger: this.fetchTrigger,
eventType: 'click',
ajaxCallback: this.handleClick,
ajaxUrl: this.fetchUrl,
});
}

private handleClick(ajaxContainer: HTMLElement): void {
private handleClick(ajaxContainer: HTMLElement, ajaxUrl: string): void {
const showAjaxLoader = true;

// TODO: Add 'data-' attribute on button to specifiy HTML source to be fetched.
fetch('ajax/ajax.html', {
fetch(ajaxUrl, {
method: 'GET',
signal: ajaxAbortHandler({
ajaxContainer,
Expand Down
15 changes: 9 additions & 6 deletions ui/src/javascript/web-components/webui-predictive-search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,41 @@ import searchResults from '../templates/search-results';
export default class WebUIPredictiveSearch extends HTMLElement {
private searchForm: HTMLFormElement | null;
private searchInput: HTMLInputElement | null;
private fetchUrl: string | undefined;

constructor() {
super();

this.searchForm = this.querySelector('[role="search"]');
this.searchInput = this.querySelector('[type="search"]');
this.fetchUrl = this.searchInput?.dataset.fetchUrl;

if (!this.searchForm || !this.searchInput) return;
if (!this.searchForm || !this.searchInput || !this.fetchUrl) return;

ajaxEventHandler({
ajaxTrigger: this.searchInput,
eventType: 'keyup',
ajaxCallback: this.handleKeyUp,
ajaxUrl: this.fetchUrl,
});

this.searchForm.addEventListener('submit', this);
}

private handleKeyUp = (ajaxContainer: HTMLElement): void => {
private handleKeyUp = (
ajaxContainer: HTMLElement,
ajaxUrl: string,
): void => {
const showAjaxLoader = true;
const query = this.searchInput?.value.toLowerCase();

// API paths would normally be defined in a global config.
const API_PATH = 'https://pokeapi.co/api/v2/pokemon?limit=1000';

if (!query) {
ajaxContainer.innerHTML = '';
return;
}

if (query && query?.length > 2) {
fetch(API_PATH, {
fetch(ajaxUrl, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Expand Down
8 changes: 4 additions & 4 deletions ui/stories/1. Storybook Intro/1-Introduction.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { Meta } from '@storybook/blocks';
# Web UI Boilerplate Storybook
This is an accessible UI boilerplate and component library for websites & webapps.

It comprises
- global design system tokens - colours, typography, icons
- fully functioning UI components and layouts, which can be optionally configured using Storybook's `controls` addon to demonstrate variations in behaviour & layout
- useful utilities & helpers
It comprises:
- Global design system tokens - colours, typography, icons.
- Fully functioning UI components and layouts, which can be optionally configured using Storybook's `controls` addon to demonstrate variations in behaviour & layout.
- Useful utilities & helpers.

```
No external frameworks have been harmed in the making of this boilerplate!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const WebUIFetchHtmlHtml = () => `
<button
class="button button--text button--primary"
data-fetch-target="test-1"
data-fetch-url="ajax/ajax.html"
>
Fetch HTML
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Attribute | Behaviour
--- | ---
`data-fetch-target` | Required. On the button that triggers the Fetch request. The value must match `data-fetch-container`.
`data-fetch-container` | Required. On the ARIA live region. The value must match `data-fetch-target`.
`data-fetch-url` | Required. URL of HTML fragment to be fetched.

## TODO
- Add `data-` attribute on button to specifiy HTML source to be fetched.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const WebUIPredictiveSearchHtml = () => `
placeholder="Search for a Pokemon"
required
data-fetch-target="search-results"
data-fetch-url="https://pokeapi.co/api/v2/pokemon?limit=1000"
/>
</div>
<button type="submit" class="button button--text button--positive">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@ Attribute | Behaviour
--- | ---
`data-fetch-target` | Required. On the button that triggers the Fetch request. The value must match `data-fetch-container`.
`data-fetch-container` | Required. On the ARIA live region. The value must match `data-fetch-target`.
`data-fetch-url` | Required. API endpoint for data to be fetched.

0 comments on commit 4fc284a

Please sign in to comment.