# Common Utilities: Usage & Architecture

## Goals

This common utilities package provides:

- A configurable **Logger** service for debug output and log capture.
- A lightweight **HTTP wrapper** around `fetch`.
- Higher-level **I/O helpers** to talk to PHP endpoints.
- **DOM loaders** to fetch HTML/JSON and inject into the page.
- A simple **logging backend** in PHP: `write.php` (append logs) and `view-log.php` (view logs).
- Small **array helpers** for fixed-size arrays.

Design constraints:

- Vanilla JS, no jQuery, no bundler.
- PHP + HTML + Bootstrap 5 + Font Awesome + MySQL/PDO stack.
- No hard-coded absolute paths; use relative paths so the code can move across shared servers.
- Reused across projects, each with its own `APP_NAME`.

---

## Directory Layout

Recommended structure (under web root):

```text
common/
  js/
    common/
      logger.js
      http.js
      io.js
      domLoaders.js
      arrays.js
  logs/
    <appName>/
      <appName>-YYYY-MM-DD.log
  php/
    write.php
    view-log.php

tests/
  test-common-utils.html
  test-content.html
  test-json.php
```

Each project includes the JS files and defines `APP_NAME`.

---

## Including Scripts in a Project

In a project page (PHP or HTML):

```html
<script src="/common/js/common/logger.js"></script>
<script src="/common/js/common/http.js"></script>
<script src="/common/js/common/io.js"></script>
<script src="/common/js/common/domLoaders.js"></script>
<script src="/common/js/common/arrays.js"></script>

<script>
  // Project identifier; used as log subdirectory name.
  const APP_NAME = 'myProject'; // e.g., 'boardGame', 'adminPanel', etc.

  // Initialize logger for this project.
  const logger = new Logger({
    debug: 5,
    debugLow: 1,
    debugHigh: 9,
    defaultTarget: '#divDebug' // element where screen notifications go
  });
</script>
```

---

## Logger (`logger.js`)

### Purpose

Provide a common debug/logging API with:

- Level/range gating.
- Console logging.
- DOM/alert notifications.
- Captured log buffer for later persistence.

### Class: `Logger`

#### Constructor

```js
const logger = new Logger({
  debug: 5,                  // 0 disables logging entirely
  debugLow: 1,               // min level to log
  debugHigh: 9,              // max level to log
  defaultTarget: '#divDebug' // optional default DOM target for notify()
});
```

#### Configuration

- `updateLevels({ debug, debugLow, debugHigh })`

  ```js
  logger.updateLevels({ debug: 3, debugLow: 1, debugHigh: 5 });
  ```

- `shouldLog(level: number): boolean`

  Internal check: returns `true` if the given level is within the configured range.

#### Console Logging

All of these:

- Respect the debug range.
- Append messages to an internal buffer when capture is enabled.

- `log(level, msg)`
- `info(level, msg)`
- `warn(level, msg)`
- `error(level, msg)`

Examples:

```js
logger.info(3, 'App initialization complete');
logger.warn(4, 'Missing optional configuration parameter X');
logger.error(2, 'Failed to load resource: /api/data.php');
```

#### User Notifications

- `notify({ level, mode, message, target, append, className })`

Parameters:

- `level`: debug level for gating.
- `mode`: `'alert'` or `'screen'`.
- `message`: text to show.
- `target`: CSS selector or `HTMLElement` (for `'screen'` mode).
  - Defaults to `defaultTarget` if not provided.
- `append`: `true` to append, `false` to overwrite.
- `className`: CSS class for the message span (e.g., `'info'`, `'warn'`, `'error'`).

Example:

```js
logger.notify({
  level: 3,
  mode: 'screen',
  message: 'Settings saved successfully.',
  target: '#divStatusLine',
  append: false,
  className: 'info'
});
```

#### Buffer & Persistence

- `enableCapture()`, `disableCapture()`

  Toggle whether log messages are stored in the internal buffer.

- `getLog(): string`

  Returns the full log buffer.

- `clearLog()`

  Resets the buffer to a header line.

- `flushToFile(writerFn, fileName, mode)`

  Calls `writerFn` with the current buffer:

  ```js
  logger.flushToFile(
    (content /*, fileName, mode */) =>
      writeAppLog({
        appName: APP_NAME,
        content,
        logger
      }),
    '',
    ''
  );
  ```

`writerFn` is typically a wrapper that POSTs to `/common/php/write.php`.

---

## HTTP Wrapper (`http.js`)

### Purpose

Provide a small, reusable wrapper around `fetch`:

- Timeout support.
- Simple JSON/text handling.
- Basic error messages.

### Function: `httpRequest(options)`

```js
async function httpRequest({
  url,
  method = 'GET',
  data = null,               // string, FormData, or object
  responseType = 'text',     // 'text' | 'json'
  headers = {},
  timeoutMs = 10000,
  cache = 'no-cache'
}): Promise<string | object>
```

#### Behavior

- Uses `AbortController` to enforce `timeoutMs`.
- Body handling:
  - `FormData` → sent as is.
  - `string` → sent with `Content-Type: text/plain;charset=UTF-8` (if not already set).
  - object → JSON-encoded with `Content-Type: application/json;charset=UTF-8` (if not set).
- Error handling:
  - Throws `Error("Network error: ...")` on fetch errors.
  - Throws `Error("HTTP <status> <statusText> - <body>")` on non-2xx responses.

#### Examples

```js
// Text response
const html = await httpRequest({
  url: '/tests/test-content.html',
  method: 'GET',
  responseType: 'text'
});

// JSON response
const data = await httpRequest({
  url: '/tests/test-json.php',
  method: 'GET',
  responseType: 'json'
});
```

---

## I/O Helpers (`io.js`)

### Purpose

Build on top of `httpRequest` to:

- Write arbitrary data/files to PHP endpoints.
- Save app-specific settings and trigger UI updates.

### Function: `writeFileViaHttp(options)`

```js
async function writeFileViaHttp({
  url,             // e.g. '/common/php/write.php'
  fileName,        // may be unused depending on server implementation
  data,
  mode = 'w',
  logger,
  statusSelector = '#divStatusLine'
}): Promise<{ ok: boolean, data?: string, error?: string }>
```

#### Behavior

- POSTs `{ file: fileName, data, mode }` to `url`.
- If response begins with `"ERROR:"`, treats as error.
- Updates `statusSelector` (color + text).
- Logs via `logger` if provided.

> Note: If you’re using the per-app logging `write.php` variant, `fileName` and `mode` may not be used; you can wrap `httpRequest` yourself (see `writeAppLog` below).

### Example: project-specific log writer

```js
async function writeAppLog({ appName, content, logger, statusSelector = '#divStatusLine' }) {
  const statusEl = document.querySelector(statusSelector);

  try {
    const responseText = await httpRequest({
      url: '/common/php/write.php',
      method: 'POST',
      responseType: 'text',
      data: {
        app: appName,
        data: content
      }
    });

    if (responseText.startsWith('ERROR:')) {
      if (statusEl) {
        statusEl.style.background = 'red';
        statusEl.style.color = 'white';
        statusEl.textContent = responseText;
      }
      logger?.error(2, 'writeAppLog error: ' + responseText);
      return { ok: false, error: responseText };
    }

    if (statusEl) {
      statusEl.style.background = 'green';
      statusEl.style.color = 'white';
      statusEl.textContent = responseText;
    }
    logger?.info(2, 'writeAppLog success: ' + responseText);
    return { ok: true, data: responseText };
  } catch (err) {
    const msg = 'Request failed: ' + err.message;
    if (statusEl) {
      statusEl.style.background = 'red';
      statusEl.style.color = 'white';
      statusEl.textContent = msg;
    }
    logger?.error(1, msg);
    return { ok: false, error: err.message };
  }
}
```

Used with `Logger.flushToFile`:

```js
logger.flushToFile(
  (content) => writeAppLog({ appName: APP_NAME, content, logger }),
  '',
  ''
);
```

---

## DOM Loaders (`domLoaders.js`)

### Purpose

Replace jQuery-based AJAX patterns with a generic, class-driven loader using `fetch`.

### Helper: `resolveTarget(input)`

- Input: `Element` or URL `string`.
- Output: `{ url, classNames }`.

Usage:

```js
const { url, classNames } = resolveTarget(linkElement);
```

### Helper: `autoCollapseBootstrapNav()`

- Locates `#divNavCollapse.navbar-collapse`.
- If it has `show` or `in`, removes those from `.navbar-collapse` elements.
- Used to auto-close mobile nav after clicking an AJAX link.

### Helper: `renderLoadedContent({ container, html, url, classNames })`

- `ajaxPre` in `classNames` → `<pre>` wrapper.
- `ajaxIframe` in `classNames` → `<iframe src="url"...>`.
- Else → `container.innerHTML = html`.

### Main Function: `loadInto(options)`

```js
async function loadInto({
  source,               // Element or URL string
  output,               // Element or selector string
  method = 'GET',
  useProxy = false,
  proxyUrl = '/common/php/phpProxy1.php',
  responseType = 'text', // 'text' | 'json'
  logger,
  statusSelector = '#divStatusLine'
}): Promise<{ ok: boolean, data?: any, error?: string }>
```

#### Behavior

1. Calls `resolveTarget(source)` → `{ url, classNames }`.
2. If `classNames` includes `'ajaxJson'`, sets `responseType = 'json'`.
3. If `useProxy` or `classNames` includes `'ajaxProxy'`, rewrites:
   - `url = proxyUrl + '?url=' + encodeURIComponent(url)`.
4. Calls `autoCollapseBootstrapNav()`.
5. Performs `httpRequest({ url, method, responseType })`.
6. Renders result into `output` using `renderLoadedContent`.
   - JSON is pretty-printed into `<pre>`.
7. On error:
   - Logs via `logger.error`.
   - Writes human-readable error into `statusSelector`.

#### Examples

**Bind to toolbar links:**

```js
document.querySelectorAll('#divToolBar a.ajaxTest').forEach((a) => {
  a.addEventListener('click', (event) => {
    event.preventDefault();
    loadInto({
      source: event.currentTarget,
      output: '#divContent',
      method: 'GET',
      logger,
      statusSelector: '#divStatusLine'
    });
  });
});
```

**Direct URL load:**

```js
loadInto({
  source: 'tests/test-content.html',
  output: '#divContent',
  method: 'GET',
  logger
});

// JSON test
loadInto({
  source: 'tests/test-json.php',
  output: '#divContent',
  method: 'GET',
  responseType: 'json',
  logger
});
```

---

## Array Helpers (`arrays.js`)

### Function: `create2dArray(rows, columns, defaultValue?)`

Produce a 2D array of fixed size:

```js
const grid = create2dArray(8, 8, 0);
// grid === 0, grid.length === 8, grid.length === 8
```

### Function: `createArray(size, defaultVal?)`

Produce a 1D array:

```js
const row = createArray(5, null);
// row = [null, null, null, null, null]
```

---

## Logging Backend (`write.php`, `view-log.php`)

### `/common/php/write.php`

**Purpose:**

Append log lines to per-app, daily log files using **relative paths**:

- Base: `common/logs/`
- Per app: `common/logs/<appName>/`
- File: `<appName>-YYYY-MM-DD.log`

**Input (POST):**

- `app`: app name (e.g. `boardGame`).
- `data`: string payload (typically the captured log buffer).

**Behavior:**

- Uses `__DIR__ . '/../logs'` so it’s relative to `write.php`, not the server root.
- Sanitizes app name to avoid path traversal.
- Ensures `common/logs/appName/` exists and is writable.
- Writes `"[ISO8601] <data>\n"` to `<appName>-YYYY-MM-DD.log` using `file_put_contents` with `FILE_APPEND | LOCK_EX`.

### `/common/php/view-log.php`

**Purpose:**

View logs in the browser for a given app (and optional date).

**Input (GET):**

- `app` (required): app name.
- `date` (optional): `YYYY-MM-DD`.
  - If omitted: selects the newest `<app>-YYYY-MM-DD.log`.

**Behavior:**

- Determines app log directory: `common/logs/<app>/`.
- Chooses log file (specific date or latest).
- Reads file content via `file_get_contents`.
- Escapes content with `htmlspecialchars` and prints inside `<pre>`.

**Usage:**

- Latest log for `boardGame`:

  ```text
  /common/php/view-log.php?app=boardGame
  ```

- Log for a specific date:

  ```text
  /common/php/view-log.php?app=boardGame&date=2026-06-18
  ```

---

## Typical Project Usage Flow

1. **Bootstrap**

   ```js
   const APP_NAME = 'boardGame';

   const logger = new Logger({
     debug: 5,
     debugLow: 1,
     debugHigh: 9,
     defaultTarget: '#divDebug'
   });
   ```

2. **Use logger where needed**

   ```js
   logger.info(3, 'BoardGame app started');
   logger.warn(4, 'Config value X is missing, using default');
   ```

3. **Load server content**

   ```js
   loadInto({
     source: 'tests/test-content.html',
     output: '#divContent',
     method: 'GET',
     logger
   });
   ```

4. **Save log to server**

   ```js
   document.getElementById('btnSaveLog').addEventListener('click', async () => {
     const result = await logger.flushToFile(
       (content) => writeAppLog({ appName: APP_NAME, content, logger }),
       '',
       ''
     );

     if (!result || result.ok === false) {
       alert('Saving log failed: ' + (result?.error || 'unknown error'));
     } else {
       alert('Log saved successfully.');
     }
   });
   ```

5. **View logs**

   - Admin link:

     ```html
     <a href="/common/php/view-log.php?app=boardGame" target="_blank">
       View boardGame logs
     </a>
     ```

This `commonUtilUsage.md` file documents the architecture and usage patterns for the shared utilities across your projects.
