bp-popnote + BpPopnote.php

A zero-dependency click popover component with multi-page carousel support, paired with a PHP class that renders directly from database data.

No Dependencies PHP 8.0+ Single JS File 9 Themes
🖱️ Click-triggered
🎠 Auto-carousel
🐘 PHP / DB ready
🎨 9 built-in themes
📐 Auto-flip positioning
🔒 XSS-safe output

Overview

bp-popnote is a lightweight, self-contained popover component designed for desktop web applications. It works with any PHP page by including a single JS file — no Bootstrap, no jQuery, no build step required.

The companion BpPopnote.php class lets you render popover triggers and their content directly from PHP variables or database query results, handling ID generation, HTML escaping, and newline conversion automatically.

Design scope
Optimised for desktop / widescreen layouts. Mobile and accessibility (ARIA) support are intentionally out of scope for this release.

Files

your-project/
  ├── bp-popnote.js    ← JS component (CSS injected automatically)
  ├── BpPopnote.php  ← PHP render class
  └── your-page.php  ← your page, require BpPopnote.php

That's it. No npm install, no composer, no bundler.

Quick Start

Plain HTML

<!-- 1. Add data attributes to any element -->
<span
  data-popover-title="Aspirin"
  data-popover-content="Analgesic, antipyretic, anti-inflammatory."
  data-popover-theme="warning"
>Aspirin</span>

<!-- 2. Include the script before </body> -->
<script src="bp-popnote.js"></script>

PHP + Database

<?php
require_once 'BpPopnote.php';
$drug = $pdo->query("SELECT * FROM drugs WHERE id = 1")->fetch();
?>

<!-- Inline short text -->
<?= BpPopnote::inline($drug['name'], $drug['indication'], ['theme' => 'warning']) ?>

<!-- Multi-page carousel from DB row -->
<?= BpPopnote::fromArray($drug['name'], 'Drug Info', [
    'Dosage'        => $drug['dosage'],
    'Contraindication' => $drug['contraindication'],
    'Interactions'  => $drug['interactions'],
], ['theme' => 'warning']) ?>

<!-- Script tag (call once, renders only once even if called multiple times) -->
<?= BpPopnote::script('/assets/bp-popnote.js') ?>

HTML Usage (data attributes)

Short inline content

<span
  data-popover-title="Title"
  data-popover-content="Short text or simple HTML"
  data-popover-theme="info"
>trigger text</span>

Large HTML block (single page)

<span data-popover-title="Title" data-popover-target="#my-note">trigger</span>

<template id="my-note">
  <table>...</table>
  <p>Any HTML here.</p>
</template>

Carousel (auto-detected)

Place two or more direct child <section> elements inside the <template>. The carousel activates automatically — no extra attribute needed.

<span
  data-popover-title="Step by Step"
  data-popover-target="#steps"
  data-popover-interval="4000"
>View Steps</span>

<template id="steps">
  <section>Step 1 content</section>
  <section>Step 2 content</section>
  <section>Step 3 content</section>
</template>
Auto-flip positioning
The popover detects viewport edges and automatically flips direction (top ↔ bottom, left ↔ right) to stay fully visible.

PHP Class Usage

BpPopnote::inline()

Short plain text or simple HTML. escape defaults to true.

BpPopnote::inline(
    'trigger label',
    'description text',
    ['title' => 'Tooltip Title', 'theme' => 'info']
)

BpPopnote::html()

Large HTML block. You assemble the HTML; escape defaults to false.

$html = '<table>...</table>';

BpPopnote::html($row['name'], $html, [
    'title' => 'Detail',
    'theme' => 'warning',
])

BpPopnote::carousel()

Multi-page carousel. Each array key becomes a page heading; values are HTML strings.

BpPopnote::carousel($emp['name'], 'Employee Info', [
    'Profile'     => '<p>...</p>',
    'Health'      => '<p>...</p>',
    'Review'      => '<p>...</p>',
], ['theme' => 'info', 'interval' => 4000])

BpPopnote::fromArray()

Pass a DB row directly. Every value is treated as plain text — htmlspecialchars + nl2br applied automatically. Each key becomes a carousel page heading.

BpPopnote::fromArray($product['name'], 'Product Detail', [
    'Category'    => $product['category'],
    'Description' => $product['description'],  // \n auto → <br>
    'Stock'       => $product['stock'],
], ['theme' => 'highlight'])

BpPopnote::script()

Outputs the <script> tag and optional global config. Safe to call multiple times — renders only once.

BpPopnote::script('/assets/bp-popnote.js', [
    'theme'       => 'dark',
    'maxWidth'    => '520px',
    'borderStyle' => 'solid',
    'carousel'    => ['interval' => 3000],
])

Themes

Set globally via PopoverConfig.set({ theme: 'info' }) or per-element via data-popover-theme / PHP 'theme' option.

dark
#240518 · #C3A5E5
info
#0c1a2e · #08a9d1
warning
#1e0e0e · #F08080
success
#0a1e18 · #40c99a
highlight
#1a1e04 · #C8DD5A
note
#1e1812 · #DECA4B
pink
#1e0818 · #FFB3D9
salmon
#1e1210 · #E5C3B3
safe
#0c1826 · #5fafed

Custom theme

PopoverConfig.addTheme('brand', {
  bg:          '#0a0f1e',
  titleColor:  '#f0a500',
  borderColor: '#f0a500',
});

Options Reference

data-* attributes (HTML)

AttributeValuesDefaultDescription
data-popover-titlestringPopover header text
data-popover-contentstringInline content (short text / HTML)
data-popover-target#idPoints to a <template> element
data-popover-themetheme namedarkVisual theme
data-popover-placementtop / bottom / left / righttopPreferred direction (auto-flips)
data-popover-bordersolid / dashed / dottedsolidBorder style
data-popover-fontsizeCSS value1remFont size inside popover
data-popover-maxwidthCSS value560pxMax width
data-popover-arrowtrue / falsetrueShow directional arrow
data-popover-hinttrue / falsetrueDashed underline on trigger
data-popover-intervalms / 03000Carousel auto-play interval; 0 = off
data-popover-carousel-animslide / crossfadeslideCarousel transition style

PHP $options array keys

KeyTypeDefaultDescription
titlestringsame as $labelPopover header text
themestringdarkTheme name
placementstringtopPreferred direction
borderstringsolidBorder style
fontsizestring1remFont size
maxwidthstringMax width override
arrowbooltrueShow arrow
hintbooltrueShow dashed underline
intervalint3000Carousel interval ms
carousel_animstringslideCarousel animation
escapeboolmethod-dependentAuto htmlspecialchars + nl2br
tagstringspanTrigger element tag name
classstringExtra CSS class on trigger

Global Config (JS)

PopoverConfig.set({
  theme:       'dark',      // default theme
  maxWidth:    '560px',
  offset:      8,           // px gap between trigger and popover
  fontSize:    '1rem',
  borderStyle: 'solid',    // solid | dashed | dotted
  placement:   'top',
  carousel: {
    animation: 'slide',    // slide | crossfade
    interval:  3000,       // ms; 0 = no auto-play
  },
});

Newline & HTML Handling

Methodescape default\n<br>Use when
inline()true✅ autoShort plain-text DB fields
html()falsemanualYou assemble the HTML yourself
carousel()falsemanualEach page is an HTML string you build
fromArray()true (forced)✅ autoDump a DB row directly, no HTML needed
Security note
When assembling HTML manually for html() or carousel(), always call htmlspecialchars() on untrusted field values before embedding them. For fields containing newlines, chain nl2br(htmlspecialchars($value, ENT_QUOTES, 'UTF-8')).

License

MIT License — free to use, modify, and distribute in personal and commercial projects.