Skip to the content.

← Back to JavaScript Development

QR Code Controller

Status: Active
Controller: qrcode-controller.js
NPM Package: qrcode ^1.5.4

Overview

The QR Code Stimulus controller generates QR codes dynamically using the qrcode npm package. Supports lazy generation in modals, download as PNG, and copy to clipboard.

Controller API

Location

File: app/assets/js/controllers/qrcode-controller.js

Static Targets

static targets = ["canvas"]

Static Values

static values = {
    url: String,                                              // URL to encode (required)
    size: { type: Number, default: 256 },                     // QR code size in pixels
    modalId: String,                                          // Optional modal ID for lazy generation
    colorDark: { type: String, default: '#000000' },          // QR code dark color
    colorLight: { type: String, default: '#ffffff' },         // QR code light color
    errorCorrectionLevel: { type: String, default: 'H' }      // L, M, Q, or H
}

Note: The default errorCorrectionLevel is H (High, ~30% damage tolerance), not M.

Methods

connect()

Sets up the controller. If modalIdValue is provided, listens for the Bootstrap shown.bs.modal event to defer generation. Otherwise generates immediately.

connect() {
    this.generated = false;
    if (this.hasModalIdValue) {
        const modal = document.getElementById(this.modalIdValue);
        if (modal) {
            this._onModalShown = () => this.generate();
            modal.addEventListener('shown.bs.modal', this._onModalShown);
        }
    } else {
        this.generate();
    }
}

disconnect()

Removes the modal event listener to prevent memory leaks.

generate()Promise

Generates the QR code. Returns a Promise. Only generates once (guard via this.generated flag).

Error handling:

generate() {
    if (this.generated) return Promise.resolve();
    if (!this.hasUrlValue) throw new Error('QR Code: URL value is required');
    if (!this.hasCanvasTarget) throw new Error('QR Code: Canvas target is required');

    this.canvasTarget.innerHTML = '';
    const canvas = document.createElement('canvas');
    this.canvasTarget.appendChild(canvas);

    return new Promise((resolve, reject) => {
        QRCode.toCanvas(canvas, this.urlValue, {
            width: this.sizeValue,
            margin: 2,
            color: { dark: this.colorDarkValue, light: this.colorLightValue },
            errorCorrectionLevel: this.errorCorrectionLevelValue
        }, (error) => {
            if (error) {
                this.canvasTarget.innerHTML = '<p class="text-danger">Error generating QR code</p>';
                reject(error);
            } else {
                this.generated = true;
                resolve();
            }
        });
    });
}

regenerate()

Resets the generated flag and calls generate(). Use when the URL changes dynamically.

download()async

Downloads the QR code as a PNG file. Awaits generate() first, then finds the <canvas> element inside the target div and uses canvas.toDataURL('image/png') to create a download link.

async download() {
    try {
        await this.generate();
        const canvas = this.canvasTarget.querySelector('canvas');
        if (!canvas) { console.error('Canvas not found'); return; }
        const link = document.createElement('a');
        link.download = 'qrcode.png';
        link.href = canvas.toDataURL('image/png');
        link.click();
    } catch (error) {
        console.error('QR Code generation failed:', error);
    }
}

copyToClipboard()async

Copies the QR code to clipboard as a PNG image using the Clipboard API. Awaits generate(), then converts the canvas to a blob.

async copyToClipboard() {
    try {
        await this.generate();
        const canvas = this.canvasTarget.querySelector('canvas');
        if (!canvas) { console.error('Canvas not found'); return; }
        const blob = await new Promise((resolve, reject) => {
            canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('Failed to create blob')));
        });
        await navigator.clipboard.write([new ClipboardItem({ 'image/png': blob })]);
    } catch (error) {
        console.error('Failed to copy QR code:', error);
    }
}

Registration

The controller registers itself via the standard window.Controllers pattern:

if (!window.Controllers) {
    window.Controllers = {};
}
window.Controllers["qrcode"] = QrcodeController;

It is also explicitly imported in index.js:

import './controllers/qrcode-controller.js';

Usage Examples

Basic Usage

<div data-controller="qrcode"
     data-qrcode-url-value="https://example.com/event/123">
    <div data-qrcode-target="canvas"></div>
</div>

Important: The canvas target should be a <div>, not a <canvas>. The controller creates the <canvas> element dynamically inside it.

With Custom Size and Colors

<div data-controller="qrcode"
     data-qrcode-url-value="https://example.com/event/123"
     data-qrcode-size-value="300"
     data-qrcode-color-dark-value="#1a5490"
     data-qrcode-color-light-value="#f0f0f0">
    <div data-qrcode-target="canvas"></div>
</div>

Lazy Loading in Modal

QR code generation is deferred until the Bootstrap modal is shown:

<button type="button" data-bs-toggle="modal" data-bs-target="#qrModal">
    Show QR Code
</button>

<div class="modal fade" id="qrModal" tabindex="-1">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Event QR Code</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
            </div>
            <div class="modal-body text-center"
                 data-controller="qrcode"
                 data-qrcode-url-value="https://example.com/event/123"
                 data-qrcode-modal-id-value="qrModal">
                <div data-qrcode-target="canvas"></div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-primary"
                        data-action="click->qrcode#download">
                    <i class="bi bi-download"></i> Download
                </button>
                <button type="button" class="btn btn-secondary"
                        data-action="click->qrcode#copyToClipboard">
                    <i class="bi bi-clipboard"></i> Copy
                </button>
            </div>
        </div>
    </div>
</div>

Error Correction Levels

Level Tolerance Description
L ~7% Low
M ~15% Medium
Q ~25% Quartile
H ~30% High (default)
<div data-controller="qrcode"
     data-qrcode-url-value="https://example.com"
     data-qrcode-error-correction-level-value="L">
    <div data-qrcode-target="canvas"></div>
</div>

CakePHP Integration Example

Used on gathering public landing pages:

<div class="modal-body text-center"
     data-controller="qrcode"
     data-qrcode-url-value="<?= $this->Url->build([
         'controller' => 'Gatherings',
         'action' => 'publicLanding',
         $gathering->public_id
     ], ['fullBase' => true]) ?>"
     data-qrcode-size-value="300"
     data-qrcode-modal-id-value="qrCodeModal">
    <div data-qrcode-target="canvas" class="mx-auto"></div>
</div>

Troubleshooting

Problem Check
QR code not generating Console for thrown Error; verify data-qrcode-url-value is set
Modal QR not appearing data-qrcode-modal-id-value must match the modal’s id attribute
Download not working Ensure canvas has rendered; check browser console
Clipboard copy failing Requires HTTPS in production; check Clipboard API support

Quick Reference

<!-- Basic -->
<div data-controller="qrcode" data-qrcode-url-value="https://example.com">
    <div data-qrcode-target="canvas"></div>
</div>

<!-- Actions -->
<button data-action="click->qrcode#download">Download</button>
<button data-action="click->qrcode#copyToClipboard">Copy</button>
<button data-action="click->qrcode#regenerate">Refresh</button>

<!-- In Modal (lazy) -->
<div data-controller="qrcode"
     data-qrcode-url-value="..." data-qrcode-modal-id-value="myModal">
    <div data-qrcode-target="canvas"></div>
</div>

Values: | Value | Type | Default | Required | |——-|——|———|———-| | url | String | — | ✅ | | size | Number | 256 | ❌ | | modalId | String | — | ❌ | | colorDark | String | #000000 | ❌ | | colorLight | String | #ffffff | ❌ | | errorCorrectionLevel | String | H | ❌ |

See Also