Media File Manager

File Manager

Progressive-enhancement file browser. You write the full HTML structure once; wf-file-manager.js queries those elements and wires up all interactivity. A live read-only demo below lets you explore all three view modes, the preview panel, filtering, sorting, and breadcrumb navigation against a real demo folder.

Key capabilities:

  • Three view modes - thumbnail grid , list , and table - persisted in localStorage
  • Drag-and-drop and click-to-select upload with per-file XMLHttpRequest progress bars
  • Inline folder creation and multi-file deletion
  • Breadcrumb navigation for subfolder traversal
  • Collapsible preview panel with file metadata, persisted in localStorage
  • Sidebar filter by file type and sort control

Live Demo

Fully functional file manager running against a read-only demo folder. Browse, filter, sort, switch view modes and open the preview panel. Upload and delete actions return simulated responses — no files are modified.

Demo mode — uploads and deletions are simulated. The demo folder is read-only.

    Required Markup

    The component expects a root element marked with data-wf-file-manager and data-wf-url . All child elements below must be present before the script initialises — the JS fills dynamic content only (breadcrumbs, file grid, upload rows, preview data).

    Minimal complete structure. Omit or reorder sections to match your layout.

    HTML
    <div class="wf-fm"
         data-wf-file-manager
         data-wf-url="/api/api-file-manager.php"
         data-wf-height="560px">
    
      <!-- Toolbar -->
      <div class="wf-fm-toolbar">
    
        <!-- File upload (WojoFileSelect injects its own trigger button) -->
        <div class="wf-fm-file-wrap wf-file">
          <input type="file" multiple>
        </div>
    
        <!-- New folder -->
        <div class="wf-fm-folder-wrap">
          <input class="wf-fm-folder-name" type="text" placeholder="Folder name">
          <button class="wf-fm-folder-add" type="button">New Folder</button>
        </div>
    
        <!-- Delete selected -->
        <button class="wf-fm-del-btn" type="button">Delete</button>
    
        <!-- View toggles -->
        <div class="wf-fm-view-btns">
          <button class="wf-fm-view-btn" data-view="thumb" type="button">Grid</button>
          <button class="wf-fm-view-btn" data-view="list"  type="button">List</button>
          <button class="wf-fm-view-btn" data-view="table" type="button">Table</button>
        </div>
    
        <!-- Preview panel toggle -->
        <button class="wf-fm-preview-toggle" type="button">Preview</button>
    
      </div>
    
      <!-- Body -->
      <div class="wf-fm-body">
    
        <!-- Sidebar -->
        <nav class="wf-fm-sidebar">
          <ul class="wf-fm-filter-nav">
            <li class="wf-fm-filter-item" data-filter="all">All Files</li>
            <li class="wf-fm-filter-item" data-filter="pic">Images</li>
            <li class="wf-fm-filter-item" data-filter="vid">Videos</li>
            <li class="wf-fm-filter-item" data-filter="aud">Audio</li>
            <li class="wf-fm-filter-item" data-filter="doc">Documents</li>
          </ul>
          <div class="wf-fm-sort-wrap">
            <select class="wf-fm-sort-sel">
              <option value="name-asc">Name A&ndash;Z</option>
              <option value="name-desc">Name Z&ndash;A</option>
              <option value="date-desc">Newest</option>
              <option value="date-asc">Oldest</option>
              <option value="size-desc">Largest</option>
              <option value="size-asc">Smallest</option>
            </select>
          </div>
        </nav>
    
        <!-- Main panel -->
        <div class="wf-fm-main">
          <div class="wf-fm-breadcrumbs"></div>  <!-- filled by JS -->
          <ul  class="wf-fm-upload-list"></ul>   <!-- filled by JS -->
          <div class="wf-fm-grid"></div>         <!-- filled by JS -->
        </div>
    
        <!-- Preview panel -->
        <aside class="wf-fm-preview">
          <div class="wf-fm-preview-thumb"></div>  <!-- filled by JS -->
          <div class="wf-fm-preview-meta"></div>   <!-- filled by JS -->
        </aside>
    
      </div><!-- /.wf-fm-body -->
    
      <!-- Footer -->
      <div class="wf-fm-footer">
        <span data-stat="files"></span>
        <span data-stat="dirs"></span>
        <span data-stat="space"></span>
      </div>
    
    </div><!-- /.wf-fm -->

    Child elements

    Selector Role Filled by JS?
    .wf-fm-toolbar Top toolbar row No
    .wf-fm-file-wrap WojoFileSelect wrapper around the file <input> — the trigger button is injected automatically No
    .wf-fm-folder-wrap Wrapper for folder-name input + add button No
    .wf-fm-folder-name Folder-name <input> No
    .wf-fm-folder-add “New folder” <button> No
    .wf-fm-del-btn Delete selected <button> No
    .wf-fm-view-btns View-mode button group wrapper No
    .wf-fm-view-btn[data-view] View-mode toggles — values: thumb, list, table No
    .wf-fm-preview-toggle Preview-panel show/hide <button> No
    .wf-fm-filter-nav Sidebar filter list container No
    .wf-fm-filter-item[data-filter] Filter items — values: all, pic, vid, aud, doc No
    .wf-fm-sort-wrap Sort label + select wrapper No
    .wf-fm-sort-sel Sort <select> No
    .wf-fm-breadcrumbs Breadcrumb navigation trail Yes
    .wf-fm-upload-list Upload progress rows container Yes
    .wf-fm-grid File/folder card container Yes
    .wf-fm-preview Preview panel <aside>
    .wf-fm-preview-thumb Preview thumbnail area Yes
    .wf-fm-preview-meta Preview file metadata area Yes
    [data-stat] Footer stat labels — values: count, size, selected Yes
    Show More

    View Modes

    Three view modes are toggled by the .wf-fm-view-btn[data-view] buttons. The active mode is persisted to localStorage under the key wf-fm-view , so the user’s preference survives page reloads. The active button receives wf-active , and the grid container gets the corresponding modifier class.

    Mode data-view Grid class applied Description
    Thumbnail Grid thumb wf-fm-thumb-grid Autofill grid of square thumbnail cards. Min card width controlled by --wf-fm-thumb-min (default 116px).
    List list wf-fm-list-grid Compact single-column rows with icon, name, size, and date.
    Table table wf-fm-table-grid Full-width table with columns: name, type, size, and modified date.

    Upload

    Upload is handled by two mechanisms: the standard file-input inside .wf-fm-file-wrap (enhanced by WojoFileSelect) and drag-and-drop directly onto the file grid. Both paths use XMLHttpRequest with a progress event, so each file gets its own progress bar row in .wf-fm-upload-list .

    Files are POST ed to the URL supplied in data-wf-url with action=upload . The file field name is file and the current path is sent as path so the server can place the file in the correct directory. On success the component fires wf:fm-upload and refreshes the directory listing.

    Expected JSON response for a successful upload.

    JSON
    {
      "ok": true,
      "file": {
        "name":     "photo.jpg",
        "path":     "images/photo.jpg",
        "size":     204800,
        "modified": 1717977600,
        "type":     "pic"
      }
    }

    Data Attributes

    Place all attributes on the root .wf-fm element.

    Attribute Default Description
    data-wf-file-manager - Required. Marks the root element for auto-initialization on DOMContentLoaded. No value needed.
    data-wf-url "" Required. Absolute or root-relative URL of your backend API endpoint, e.g. /api/files.
    data-wf-height 560px Overall component height. Accepts any valid CSS length. Sets --wf-fm-height on the root element.
    data-wf-preview true Set to false to hide the preview panel on initialization. The user can still toggle it open.

    CSS Custom Properties

    Override on the .wf-fm element or any ancestor.

    CSS
    .wf-fm {
      --wf-fm-height:        700px;
      --wf-fm-sidebar-width: 200px;
      --wf-fm-preview-width: 260px;
      --wf-fm-thumb-min:     140px;
    }
    Property Default Description
    --wf-fm-height 560px Total component height. Also, settable via data-wf-height.
    --wf-fm-sidebar-width 164px Width of the left filter sidebar.
    --wf-fm-preview-width 200px Width of the right preview panel.
    --wf-fm-thumb-min 116px Minimum card width in thumbnail grid mode. The grid uses auto-fill so more cards fit on wider screens.

    API Endpoint

    All communication goes to the single URL you supply in data-wf-url . The action parameter tells the server what to do. All error responses should return { "ok": false, "message": "..." } ; the component will display the message string in the UI.

    GET – List directory

    HTTP
    GET /api/files?action=files&path=images
    JSON Response
    {
      "ok": true,
      "path": "images",
      "items": [
        {
          "name":     "subfolder",
          "type":     "dir",
          "size":     0,
          "modified": 1717977600
        },
        {
          "name":     "photo.jpg",
          "type":     "pic",
          "size":     204800,
          "modified": 1717977600,
          "url":      "/uploads/images/photo.jpg",
          "thumb":    "/uploads/images/photo_thumb.jpg"
        }
      ]
    }

    POST - Create folder

    HTTP
    POST /api/files
    Content-Type: application/x-www-form-urlencoded
    
    action=folder&path=images&name=new-folder
    JSON Response
    { "ok": true }

    POST - Delete files

    HTTP
    POST /api/files
    Content-Type: application/x-www-form-urlencoded
    
    action=delete&paths=["images/photo.jpg","images/old-folder"]
    JSON Response
    { "ok": true }

    POST - Upload file

    HTTP
    POST /api/files
    Content-Type: multipart/form-data
    
    action=upload&path=images
    file=<binary>
    JSON Response
    {
      "ok": true,
      "file": {
        "name":     "photo.jpg",
        "path":     "images/photo.jpg",
        "size":     204800,
        "modified": 1717977600,
        "type":     "pic"
      }
    }

    Programmatic API

    After initialization the instance is stored on the root element as el._wfFileManager .

    JS
    const root = document.querySelector('[data-wf-file-manager]')
    const fm = root._wfFileManager
    
    fm.navigate('images/avatars')  // open a sub-folder
    fm.refresh()                   // reload current directory
    fm.destroy()                   // remove all event listeners
    Method Signature Description
    navigate() navigate(path: string) Navigate to a specific subfolder path. Updates breadcrumbs and reloads the grid.
    refresh() refresh() Reload the current directory from the API without changing the path.
    destroy() destroy() Remove all event listeners registered by the component.

    Events

    All events bubble and are dispatched on the root .wf-fm element. Listen from any ancestor with addEventListener .

    JS
    document.addEventListener('wf:fm-upload', e => {
      console.log('Uploaded:', e.detail.file.name, e.detail.data)
    })
    
    document.addEventListener('wf:fm-delete', e => {
      console.log('Deleted paths:', e.detail.paths)
    })
    
    document.addEventListener('wf:fm-folder', e => {
      console.log('Folder created:', e.detail.name)
    })
    
    document.addEventListener('wf:fm-select', e => {
      console.log('Selected file:', e.detail.file)
    })
    Event detail Fired when
    wf:fm-upload { file, data } A file upload completes successfully. file is the native File object; data is the server response.
    wf:fm-delete { paths } Selected files/folders are deleted. paths is an array of relative path strings.
    wf:fm-folder { name } A new folder is created. name is the folder name string.
    wf:fm-select { file } A file card is selected in the grid. file is the file descriptor object from the API response.

    Class Reference

    Class Description
    wf-fm Root component element. Hosts all CSS custom properties and layout context.
    wf-fm-toolbar The top toolbar row contains upload, folder, delete, view-mode, and preview controls.
    wf-fm-file-wrap Wrapper for the upload <input type="file">. Enhanced by WojoFileSelect to add drag-and-drop.
    wf-fm-folder-name Text input for the new-folder name.
    wf-fm-folder-add Button that submits the new-folder request.
    wf-fm-del-btn Button that deletes all currently selected items.
    wf-fm-view-btn View-mode toggle button. Receives wf-active on the current mode. Requires data-view (thumb · list · table).
    wf-fm-preview-toggle Button that shows or hides the preview panel.
    wf-fm-body Flex container holding the sidebar, main panel, and preview panel.
    wf-fm-sidebar Left sidebar with filter navigation and sort select.
    wf-fm-filter-nav List container for filter items.
    wf-fm-filter-item Individual filter items. Receives wf-active when selected. Requires data-filter (all · pic · vid · aud · doc).
    wf-fm-sort-sel Sort order <select>. Option values: name-asc · name-desc · date-asc · date-desc · size-asc · size-desc.
    wf-fm-main Main content area holding breadcrumbs, upload list, and file grid.
    wf-fm-breadcrumbs Breadcrumb navigation trail. Populated by JS on each navigation.
    wf-fm-upload-list Container for per-file upload progress rows appended by JS during upload.
    wf-fm-grid File-card container. Gets one of three modifier classes depending on the active view mode.
    wf-fm-thumb-grid Applied to .wf-fm-grid in thumbnail view. Uses CSS auto-fill grid.
    wf-fm-list-grid Applied to .wf-fm-grid in list view. Single-column layout.
    wf-fm-table-grid Applied to .wf-fm-grid in table view. Full-width table layout.
    wf-fm-item Individual file or folder card. Receives wf-selected when checked. Modifier wf-fm-item--dir for folders; wf-fm-item--[type] for file type coloring (pic · vid · aud · doc).
    wf-fm-preview Right-side preview panel <aside>. Hidden when preview is off.
    wf-fm-preview-thumb Thumbnail/icon area inside the preview panel. Populated by JS on selection.
    wf-fm-preview-meta File metadata area inside the preview panel (name, size, type, date). Populated by JS.
    wf-fm-footer Footer bar. Contains [data-stat="files"], [data-stat="dirs"], and [data-stat="space"] spans updated by JS.
    Show More