https://github.com/chrisnajman/filter-quotes-v2
A lightweight app for displaying a collection of quotations from a JSON file using an HTML template. Short quotes use <q>, longer ones use <blockquote>, and entries can be filtered by author or tag.
https://github.com/chrisnajman/filter-quotes-v2
accessibility aria-attributes css-flexbox css-grid css-imports css-nesting cssnano details es6-modules esbuild html-css-javascript html-minifier-terser html-template json loading-spinner no-js postcss resize-observer sticky-headers theme-switcher
Last synced: 6 months ago
JSON representation
A lightweight app for displaying a collection of quotations from a JSON file using an HTML template. Short quotes use <q>, longer ones use <blockquote>, and entries can be filtered by author or tag.
- Host: GitHub
- URL: https://github.com/chrisnajman/filter-quotes-v2
- Owner: chrisnajman
- License: mit
- Created: 2025-10-10T14:45:12.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2025-10-17T08:25:00.000Z (6 months ago)
- Last Synced: 2025-10-18T11:38:51.272Z (6 months ago)
- Topics: accessibility, aria-attributes, css-flexbox, css-grid, css-imports, css-nesting, cssnano, details, es6-modules, esbuild, html-css-javascript, html-minifier-terser, html-template, json, loading-spinner, no-js, postcss, resize-observer, sticky-headers, theme-switcher
- Language: CSS
- Homepage: https://chrisnajman.github.io/filter-quotes-v2/
- Size: 129 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Filter Quotations
Menu
- [Description](#description)
- [Features](#features)
- [JSON](#json)
- [HTML ``](#html-template)
- [JavaScript](#javascript)
- [Accessibility](#accessibility)
- [Theme Toggling](#theme-toggling)
- [Testing and Compatibility](#testing-and-compatibility)
- [How to Run](#how-to-run)
- [Build & Deployment Setup for `/docs` Folder](#build--deployment-setup-for-docs-folder)
## Description
A lightweight web app for browsing and filtering a collection of quotations by author or tag. Built with semantic HTML and modern vanilla JavaScript, it uses a JSON data source and an HTML `` to generate the quotation list dynamically, with a focus on accessibility and clear structure.
Longer entries are rendered as `
` elements, while shorter ones use inline `` tags for natural quotation formatting.
[View on GitHub Pages](https://chrisnajman.github.io/filter-quotations-v2)
[Back to menu](#menu)
---
## Features
### Filtering quotations
- Displays a list of quotations, each containing one or more filter buttons (e.g. author names or tags).
- Clicking an **active** filter button shows only the quotations that include that author or tag.
- The active filter is visually highlighted across all matching buttons.
- A "Clear filters" button resets the list to show all quotations.
### Conditional rendering
- Quotations of **25 words or fewer** are displayed using a `` element (inline quotations).
- Quotations **over 25 words** are displayed using a `
` element (block-level quotations).
- This enhances readability while maintaining semantic meaning.
### Details dropdown
- Keyboard support: pressing `Escape` closes the currently focused ``.
- ARIA attributes (`aria-expanded`) synchronized with open/close state.
[Back to menu](#menu)
---
## JSON
The structure of a JSON entry is as follows:
```json
{
"prequote": "Pre-quote",
"quote": "Quote",
"postquote": "Pre-quote",
"author": "Author",
"tags": ["Tag-1", "Tag-2", "Tag-3", "Tag-4", "Tag-5", "Tag-(n)"]
}
```
To create your own quotations list, open `./json/quotes.json`, remove the existing entries, and add your own, using the template above as a model.
Don't forget to separate entries with a comma, i.e.
```json
[
{
"prequote": "",
"quote": "",
"postquote": "",
"author": "",
"tags": []
},
{
"prequote": "",
"quote": "",
"postquote": "",
"author": "",
"tags": []
},
{
"prequote": "",
"quote": "",
"postquote": "",
"author": "",
"tags": []
}
]
```
### Notes
- The last entry in a JSON file is **not** followed by a comma.
- Both **prequote** and **postquote** are optional. They can be used to supply a preamble or a postamble (real word!) to the quotation. If either are not required, leave the field value as `""` and the corresponding HTML will be hidden.
- If the **author** is unknown, leave the field value as `""`: a button with a label of "Unknown" will be created.
- You can create as many **tags** as you like. If you don't want any, leave the field value as an empty array (`[]`) and the entire list item will be hidden.
[Back to menu](#menu)
---
## HTML ``
The JSON entries are inserted into an HTML `` via `quotes-display.js`.
The `` element provides a reusable structure for each quotation, allowing the script to clone and populate it with data from the JSON file without repeating markup in the HTML.
```html
-
Author:
```
[Back to menu](#menu)
---
## JavaScript
Built with **vanilla ES6 JavaScript**, focusing on modern syntax and browser APIs.
The JavaScript has been split into separate modules, improving code modularity:
- `index.js`: Initializes the app using an asynchronous `init()` function.
- The function first **awaits `quotesDisplay()`**, which fetches the JSON file and renders the quotations dynamically from the ``.
- Only after that process completes does it call `quotesButtons()`, ensuring that all quotation elements and filter buttons exist in the DOM before event listeners are attached.
- This sequence guarantees that filtering behavior is safely applied to dynamically generated content.
- `quotes-display.js`: Handles fetching and displaying quotations from an external JSON file using the Fetch API.
- Retrieves `quotes.json` asynchronously and checks for a successful response.
- Clones the HTML `` for each quotation, filling in quotation text, author, and tags.
- Depending on word count (determined via `word-count.js`), inserts the text into either a `` or `
` element.
- Gracefully handles missing data:
- Displays "Unknown" when the author field is empty.
- Hides the tags section if no tags exist.
- Dynamically creates `` elements for each tag and appends them to the appropriate quotation.
- If there is only one instance of an **author** or **tag**, the `disabled` attribute is attached to its ``.
- Outputs a message to the user (`#fail`) if data fails to load or the fetch request encounters an error.
- Completes rendering before the filtering logic (`quotes-buttons.js`) is executed.
- `word-count.js`: Counts the number of words in a given string.
- Exports a simple function that trims whitespace and splits the text using a regular expression (`/\s+/`) to calculate an accurate word count.
- Used by `quotes-display.js` to decide whether to render the quotation text as an inline `` (short quotations) or block-level `
` (long quotations).
- `quotes-buttons.js`: Controls the interactive filtering behavior for the quotations list:
- Selects and caches key DOM elements: the list items (`.quote`), each quote’s filter buttons, and the "Clear filters" button.
- Tracks which filter (author or tag) is currently active.
- Handles click events on quotation buttons to apply or remove filters.
- Clicking a button shows only quotations containing that author or tag.
- Clicking the same button again resets the view.
- Updates visual states:
- Highlights active filter buttons across all matching quotations.
- Enables and styles the "Clear filters" button when a filter is active.
- Disables and clears it when no filter is applied.
- Uses simple helper functions:
- `applyFilter(selectedValue)` — hides non-matching quotations and updates button states.
- `filterInactive()` — resets all filters and restores the full quotations list.
### Other
- `page-header-resize-observer.js`: Observes the `.header` element and updates the root’s `scroll-padding-top` based on header size and the `--skip-link-gap` CSS variable (in `base.css`), ensuring skip links and anchor targets scroll into view with proper spacing below a fixed or sticky header.
- `details.js`: Handles `` accessibility and behavior.
- Syncs `aria-expanded` between `` and ``.
- Supports closing via the `Escape` key and restores focus.
- `loader.js`: See [Loader Git repository](https://github.com/chrisnajman/loader)
- `theme.js`: Handles theme toggling (light/dark mode) and local storage management.
[Back to menu](#menu)
---
## Accessibility
- ARIA attributes (`aria-expanded`) dynamically updated on all `` elements.
- Escape key support: closes the open `` and returns focus to its ``.
- Quotations rendered as `
` elements for longer text benefit assistive technologies by clearly indicating quoted or cited material. Screen readers announce them as quotations, improving semantic context.
- The inline `` element is retained for shorter quotations to ensure natural, readable inline flow while remaining accessible.
### No JS
If JavaScript is disabled, a warning message is displayed and the loader animation, theme-toggler, quotations display and filtering cease to function.
[Back to menu](#menu)
---
## Theme Toggling
The application includes a dark mode and light mode toggle:
- The current theme state is stored in **local storage** and applied automatically on page reload.
- Accessible buttons with appropriate ARIA attributes are used to improve usability.
> [!IMPORTANT]
> Remember to change `const LOCAL_STORAGE_PREFIX` in `js-modules/theme.js` to a unique identifier.
[Back to menu](#menu)
---
## Testing and Compatibility
The application has been tested on the following platforms and browsers:
- **Operating System**: Windows 11
- **Browsers**:
- Google Chrome
- Mozilla Firefox
- Microsoft Edge
### Device View Testing
The layout and functionality have been verified in both browser and device simulation views to ensure responsiveness and usability.
[Back to menu](#menu)
---
## How to Run
1. Clone or download the repository to your local machine.
2. Open the project folder and start a simple HTTP server (e.g., using `Live Server` in VS Code or Python's `http.server` module).
3. Open the project in a modern browser (e.g., Chrome, Firefox, or Edge).
[Back to menu](#menu)
---
## Build & Deployment Setup for `/docs` Folder
If you want to deploy a minified version of this project to **GitHub Pages**, read on.
### 1. Install Required Packages
Run this once in your project root to install dev dependencies:
```bash
npm install
```
### 2. Run the full build process
> [!IMPORTANT]
> Any assets not described in `package.json` must be added. In the current project we don't have an `img` folder. If you create one and add images to it, you have to add this to `copy:assets`, e.g.
#### Current `package.json`
```
"copy:assets": "shx cp -r site.webmanifest favicon.ico favicon-16x16.png favicon-32x32.png apple-touch-icon.png android-chrome-192x192.png android-chrome-512x512.png docs/",
```
#### Updated `package.json` with "img"
```
"copy:assets": "shx cp -r img site.webmanifest favicon.ico favicon-16x16.png favicon-32x32.png apple-touch-icon.png android-chrome-192x192.png android-chrome-512x512.png docs/",
```
etc, etc.
Then in the terminal, run:
```bash
npm run build
```
### 3. Deploy to GitHub Pages
Once you've created a repository and pushed the files,
- go to `https://github.com/[your-name]/[your-project-name]/settings/pages`.
- Under "Build and deployment > Branch" make sure you set the branch to `main` and folder to `/docs`.
- Click "Save".
> [!NOTE]
> For a detailed description of the build process, configuration files and npm packages see my [GitHub Pages Optimised Build](https://github.com/chrisnajman/github-pages-optimised-build).
[Back to menu](#menu)