Concrete CMS form update

User
Assistant
Here’s a Concrete CMS 9–friendly version of your dashboard form that:

– Uses the built‑in CSRF security token (`Token`)
– Keeps your route with `$view->action('submit')`
– Avoids re‑generating the slug on validation errors
– Uses Concrete 9 dashboard styles (`ccm-ui`, cards, etc.)

### View: `/single_pages/…/add.php` (or your package dashboard view)
“`php

User
<script>
function deleteFeed(id) {
  ConcreteAlert.confirm(
    '<?= t("Are you sure you want to delete this feed? This will delete the feed and all its data.") ?>',
    function() {
      $.ajax({
        url: '/shopping_feed/api/delete/' + id,
        method: 'POST',
        headers: {
          'X-Requested-With': 'XMLHttpRequest',
          'Accept': 'application/json'
        },
        success: function (data) {
          if (data.success) {
            ConcreteAlert.notify({
              message: '<?= t("Feed deleted successfully!") ?>',
              type: 'success'
            });
            setTimeout(function() {
              window.location.reload();
            }, 1500);
          } else {
            ConcreteAlert.notify({
              message: '<?= t("An error occurred while processing your request.") ?>',
              type: 'error'
            });
          }
        },
        error: function () {
          ConcreteAlert.notify({
            message: '<?= t("An error occurred while processing your request.") ?>',
            type: 'error'
          });
        }
      });
    }
  );
}

function cloneFeed(id) {
  ConcreteAlert.confirm(
    '<?= t("Duplicate feed?") ?>',
    function() {
      $.ajax({
        url: '/shopping_feed/api/clone/' + id,
        method: 'POST',
        headers: {
          'X-Requested-With': 'XMLHttpRequest',
          'Accept': 'application/json'
        },
        success: function (data) {
          if (data.success) {
            ConcreteAlert.notify({
              message: '<?= t("Feed cloned successfully!") ?>',
              type: 'success'
            });
            setTimeout(function() {
              window.location.reload();
            }, 1500);
          } else {
            ConcreteAlert.notify({
              message: '<?= t("An error occurred while processing your request.") ?>',
              type: 'error'
            });
          }
        },
        error: function () {
          ConcreteAlert.notify({
            message: '<?= t("An error occurred while processing your request.") ?>',
            type: 'error'
          });
        }
      });
    }
  );
}

function copyFeed(feedurl) {
    navigator.clipboard.writeText(feedurl).then(function() {
        ConcreteAlert.notify({
            message: '<?= t("Feed URL copied to clipboard!") ?>',
            type: 'success'
        });
    }).catch(function() {
        // Fallback for older browsers
        var $tempInput = $('<input>');
        $('body').append($tempInput);
        $tempInput.val(feedurl).select();
        
        try {
            var successful = document.execCommand('copy');
            $tempInput.remove();
            
            if (successful) {
                ConcreteAlert.notify({
                    message: '<?= t("Feed URL copied to clipboard!") ?>',
                    type: 'success'
                });
            } else {
                ConcreteAlert.notify({
                    message: '<?= t("Failed to copy URL") ?>',
                    type: 'error'
                });
            }
        } catch (err) {
            $tempInput.remove();
            ConcreteAlert.notify({
                message: '<?= t("Failed to copy URL") ?>',
                type: 'error'
            });
        }
    });
}
</script>

I want to use ccm-dashboard-header-search div to add my search form so that it will come right side of ccm-dashboard-breadcrumb as all default cms pages

Assistant
Got it — to make your search bar appear at the top-right next to the breadcrumb (like core pages), render a **`ccm-dashboard-header-search`** block **before** your `.ccm-dashboard-content`. Here’s a drop-in snippet plus the controller bits and pagination tweaks.

### 1) View: put this **at the very top** of your page (above `.ccm-dashboard-content`)

<?php
defined('C5_EXECUTE') or die('Access Denied.');

use ConcreteCoreViewView;
use ConcreteCoreSupportFacadeApplication;

$view = View::getInstance();
$app  = Application::getFacadeApplication();

/** Preserve current filters (GET) */
$searchQuery = isset($searchQuery) ? $searchQuery : (string)($this->request->query->get('q') ?? '');
$automation  = isset($automation)  ? $automation  : (string)($this->request->query->get('automation') ?? '');
$limit       = isset($limit)       ? (int)$limit  : (int)($this->request->query->get('limit') ?? 10);
?>

<div class="ccm-dashboard-header-search">
  <form method="get" action="<?= $view->action('view') ?>"
        class="row row-cols-auto g-0 align-items-center">
    <div class="ccm-header-search-form input-group">

      <div class="dropdown me-2">
        <button class="btn btn-secondary dropdown-toggle" type="button"
                id="feedFilters" data-bs-toggle="dropdown" aria-expanded="false">
          <?= t('Filters') ?>
        </button>
        <div class="dropdown-menu p-3" aria-labelledby="feedFilters" style="min-width:320px">
          <div class="mb-2">
            <label class="form-label"><?= t('Automation') ?></label>
            <select class="form-select" name="automation">
              <option value=""  <?= $automation===''  ? 'selected' : '' ?>><?= t('All') ?></option>
              <option value="1" <?= $automation==='1' ? 'selected' : '' ?>><?= t('Enabled') ?></option>
              <option value="0" <?= $automation==='0' ? 'selected' : '' ?>><?= t('Disabled') ?></option>
            </select>
          </div>
          <div class="mb-2">
            <label class="form-label"><?= t('Items per page') ?></label>
            <select class="form-select" name="limit">
              <?php foreach ([10,25,50,100] as $opt) { ?>
                <option value="<?= $opt ?>" <?= ($limit==$opt)?'selected':''; ?>><?= $opt ?></option>
              <?php } ?>
            </select>
          </div>
          <div class="text-end">
            <button type="submit" class="btn btn-secondary"><?= t('Apply') ?></button>
          </div>
        </div>
      </div>

      <input class="form-control" type="text" name="q"
             value="<?= h($searchQuery) ?>" placeholder="<?= t('Search feeds…') ?>" autocomplete="off">
      <button class="btn btn-secondary" type="submit" aria-label="<?= t('Search') ?>">
        <i class="fas fa-search"></i>
      </button>
    </div>
  </form>
</div>

> Keep your existing `.ccm-dashboard-content` block exactly as is below this. The dashboard theme will float this search bar to the right of the breadcrumb automatically.

### 2) Controller: read filters from GET and pass to the view

public function view($limitArg = 10, $page = 1)
{
    $req        = $this->request;
    $q          = trim((string)$req->query->get('q', ''));
    $automation = $req->query->get('automation'); // '', '1', '0'
    $limit      = (int)($req->query->get('limit', $limitArg ?: 10));

    // Do your filtered query here (name/slug LIKE :q, automation flag, limit & page)
    [$feeds, $totalPages] = $this->feedService->searchFeeds($q, $automation, $limit, (int)$page);

    $this->set('feeds', $feeds);
    $this->set('totalPages', $totalPages);
    $this->set('currentPage', (int)$page);
    $this->set('limit', $limit);
    $this->set('searchQuery', $q);
    $this->set('automation', $automation);
}

### 3) Keep filters in pagination links

<?php
$query = http_build_query([
  'q' => $searchQuery,
  'automation' => $automation,
  'limit' => $limit,
]);
?>
<nav class="mt-4">
  <ul class="pagination justify-content-center">
    <?php for ($i = 1; $i <= $totalPages; $i++): ?>
      <li class="page-item <?= ($i == $currentPage) ? 'active' : '' ?>">
        <a class="page-link" href="<?= $this->action('view', $limit, $i) . ($query ? ('?' . $query) : '') ?>">
          <?= $i ?>
        </a>
      </li>
    <?php endfor; ?>
  </ul>
</nav>

### 4) (Optional) Remove your old “per page” dropdown
Since the **Items per page** moved into the Filters dropdown, you can delete the old per-page button group from your header buttons to keep the header clean.

That’s it — this mirrors Concrete CMS’s dashboard header search pattern and will sit exactly where you want it.

User
NOt working
User
<script>
function deleteFeed(id) {
  ConcreteAlert.confirm(
    '<?= t("Are you sure you want to delete this feed? This will delete the feed and all its data.") ?>',
    function() {
      $.ajax({
        url: '/shopping_feed/api/delete/' + id,
        method: 'POST',
        headers: {
          'X-Requested-With': 'XMLHttpRequest',
          'Accept': 'application/json'
        },
        success: function (data) {
          if (data.success) {
            ConcreteAlert.notify({
              message: '<?= t("Feed deleted successfully!") ?>',
              type: 'success'
            });
            setTimeout(function() {
              window.location.reload();
            }, 1500);
          } else {
            ConcreteAlert.notify({
              message: '<?= t("An error occurred while processing your request.") ?>',
              type: 'error'
            });
          }
        },
        error: function () {
          ConcreteAlert.notify({
            message: '<?= t("An error occurred while processing your request.") ?>',
            type: 'error'
          });
        }
      });
    }
  );
}

function cloneFeed(id) {
  ConcreteAlert.confirm(
    '<?= t("Duplicate feed?") ?>',
    function() {
      $.ajax({
        url: '/shopping_feed/api/clone/' + id,
        method: 'POST',
        headers: {
          'X-Requested-With': 'XMLHttpRequest',
          'Accept': 'application/json'
        },
        success: function (data) {
          if (data.success) {
            ConcreteAlert.notify({
              message: '<?= t("Feed cloned successfully!") ?>',
              type: 'success'
            });
            setTimeout(function() {
              window.location.reload();
            }, 1500);
          } else {
            ConcreteAlert.notify({
              message: '<?= t("An error occurred while processing your request.") ?>',
              type: 'error'
            });
          }
        },
        error: function () {
          ConcreteAlert.notify({
            message: '<?= t("An error occurred while processing your request.") ?>',
            type: 'error'
          });
        }
      });
    }
  );
}

function copyFeed(feedurl) {
    navigator.clipboard.writeText(feedurl).then(function() {
        ConcreteAlert.notify({
            message: '<?= t("Feed URL copied to clipboard!") ?>',
            type: 'success'
        });
    }).catch(function() {
        // Fallback for older browsers
        var $tempInput = $('<input>');
        $('body').append($tempInput);
        $tempInput.val(feedurl).select();
        
        try {
            var successful = document.execCommand('copy');
            $tempInput.remove();
            
            if (successful) {
                ConcreteAlert.notify({
                    message: '<?= t("Feed URL copied to clipboard!") ?>',
                    type: 'success'
                });
            } else {
                ConcreteAlert.notify({
                    message: '<?= t("Failed to copy URL") ?>',
                    type: 'error'
                });
            }
        } catch (err) {
            $tempInput.remove();
            ConcreteAlert.notify({
                message: '<?= t("Failed to copy URL") ?>',
                type: 'error'
            });
        }
    });
}
</script>
User
I have use this div ccm-dashboard-header-buttons and its automaticallu to placed in right postion under class ccm-dashboard-header-menu
BUt How we place search in cms page header next to breadcrumb in
Assistant
You’re super close—the trick is **placement + the exact inner structure**. The dashboard theme only docks the search bar if the markup is **above** `.ccm-dashboard-content` and follows the **`ccm-header-search-form` → `ccm-header-search-form-input`** pattern.

Drop this **at the very top of your view**, before `…`:

<?php
defined('C5_EXECUTE') or die('Access Denied.');
use ConcreteCoreViewView;

$view = View::getInstance();

// read current query if you want it sticky
$searchQuery = (string)($this->request->query->get('q') ?? '');
?>

<!-- HEADER SEARCH (must be before .ccm-dashboard-content) -->
<div class="ccm-dashboard-header-search">
  <div class="ccm-header-search-form ccm-ui" data-header="shopping-feed">
    <form method="get" action="<?= $view->action('view') ?>" class="row row-cols-auto g-0 align-items-center">
      <div class="ccm-header-search-form-input input-group">
        <input class="form-control" type="text" name="q"
               value="<?= h($searchQuery) ?>" placeholder="<?= t('Search feeds…') ?>" autocomplete="off">
        <button class="btn btn-secondary" type="submit" aria-label="<?= t('Search') ?>">
          <i class="fas fa-search"></i>
        </button>
      </div>
    </form>
  </div>
</div>

Then keep the rest of your page (including your existing `.ccm-dashboard-header-buttons`) as-is below:

<div class="ccm-dashboard-content">
  <!-- your cards/table/etc -->
</div>

### Why yours didn’t show up
– If you put `ccm-dashboard-header-search` **inside** `.ccm-dashboard-content`, the theme won’t hoist it to the breadcrumb area.
– It also expects the inner wrapper classes:
– `ccm-header-search-form ccm-ui`
– `ccm-header-search-form-input` around your input group

### If you absolutely must keep it in the content (fallback)
You can move it up with a tiny script:

<div id="sfmHeaderSearch" class="d-none">
  <div class="ccm-header-search-form ccm-ui" data-header="shopping-feed">
    <form method="get" action="<?= $view->action('view') ?>" class="row row-cols-auto g-0 align-items-center">
      <div class="ccm-header-search-form-input input-group">
        <input class="form-control" type="text" name="q" placeholder="<?= t('Search feeds…') ?>">
        <button class="btn btn-secondary" type="submit"><i class="fas fa-search"></i></button>
      </div>
    </form>
  </div>
</div>

<script>
$(function () {
  var html = $('#sfmHeaderSearch').html();
  $('.ccm-dashboard-header .ccm-dashboard-header-search').html(html);
});
</script>

But the **preferred** (and cleanest) fix is the first one: place the search block **above** `.ccm-dashboard-content` with the exact class structure.

Leave a Comment

Your email address will not be published. Required fields are marked *