error : tombol see more pada filter atau tapisan pencarian tidak bekerja
penyebab : Tombol “See More” menggunakan data-toggle=”collapse” yang merupakan atribut dari Bootstrap 4, sedangkan template yang dipakai menggunakan Bootstrap 5.3. Di Bootstrap 5, atribut tersebut sudah berubah menjadi data-bs-toggle=”collapse”.
kode yang diperbaiki : /lib/SearchEngine/SearchFilter.php
kode :
<?php
namespace SLiMS\SearchEngine;
use SLiMS\DB;
trait SearchFilter
{
protected $customFilter = [];
public function setCustomFilter($filter)
{
$this->customFilter = $filter;
}
public function getFilter($opac, $build = false)
{
$filter = [];
# Publish Year
list($min, $max) = $this->getYears();
$filter[] = [
'header' => __('Publication Year'),
'name' => 'years',
'type' => 'range',
'min' => $min,
'max' => $max,
'from' => $min,
'to' => $max
];
# Availability
$filter[] = [
'header' => __('Availability'),
'name' => 'availability',
'type' => 'radio',
'items' => [
[
'value' => '1',
'label' => __('On Shelf')
]
]
];
# Attachment
$filter[] = [
'header' => __('Attachment'),
'name' => 'attachment',
'type' => 'checkbox',
'items' => [
[
'value' => 'pdf',
'label' => __('PDF')
],
[
'value' => 'audio',
'label' => __('Audio')
],
[
'value' => 'video',
'label' => __('Video')
]
]
];
# Collection type
$filter[] = [
'header' => __('Collection Type'),
'name' => 'colltype',
'type' => 'checkbox',
'items' => $this->getCollectionType()
];
# GMD
$filter[] = [
'header' => __('General Material Designation'),
'name' => 'gmd',
'type' => 'checkbox',
'items' => $this->getGMD()
];
# Location
$filter[] = [
'header' => __('Location'),
'name' => 'location',
'type' => 'checkbox',
'items' => $this->getLocation()
];
# Language
$filter[] = [
'header' => __('Language'),
'name' => 'lang',
'type' => 'checkbox',
'items' => $this->getLanguage()
];
if ($build) return $this->buildFilter($opac, array_merge($filter, $this->customFilter));
return array_merge($filter, $this->customFilter);
}
public function getYears()
{
$query = DB::getInstance()->query("select min(publish_year), max(publish_year) from biblio where length(publish_year) = 4 and substring(publish_year, 1,1) <= 2 and publish_year REGEXP '^-?[0-9]+$'");
return $query->fetch(\PDO::FETCH_NUM);
}
public function getCollectionType()
{
$query = DB::getInstance()->query("select coll_type_id `value`, coll_type_name `label` from mst_coll_type");
return $query->fetchAll(\PDO::FETCH_ASSOC);
}
public function getGMD()
{
$query = DB::getInstance()->query("select gmd_id `value`, gmd_name `label` from mst_gmd");
return $query->fetchAll(\PDO::FETCH_ASSOC);
}
public function getLocation()
{
$query = DB::getInstance()->query("select location_id `value`, location_name `label` from mst_location order by location_name asc");
return $query->fetchAll(\PDO::FETCH_ASSOC);
}
public function getLanguage()
{
$query = DB::getInstance()->query("select language_id `value`, language_name `label` from mst_language order by language_name asc");
return $query->fetchAll(\PDO::FETCH_ASSOC);
}
public function buildFilter($opac, $filters): string
{
// get filter from url
$filterStr = \utility::filterData('filter', 'get', false, true, true);
$filterArr = json_decode($filterStr??'', true);
$str = '<form id="search-filter"><ul class="list-group list-group-flush">';
// $str .= '<input type="hidden" name="csrf_token" value="'.$opac->getCsrf().'">';
foreach ($this->reOrder($filters) as $index => $filter) {
if ($index < 1) {
$str .= '<li class="list-group-item bg-transparent pl-0 border-top-0">';
} else {
$str .= '<li class="list-group-item bg-transparent pl-0">';
}
// Menggunakan data-bs-toggle untuk Bootstrap 5
$str .= <<<HTML
<div class="d-flex justify-content-between align-items-center cursor-pointer" data-bs-toggle="collapse" data-bs-target="#collapse-{$index}" aria-expanded="true" aria-controls="collapse-{$index}">
<strong class="text-sm">{$filter['header']}</strong>
<i class="dropdown-toggle"></i>
</div>
<div class="collapse show text-sm" id="collapse-{$index}"><div class="mt-2">
HTML;
$value = $filterArr[$filter['name']] ?? null;
switch ($filter['type']) {
case 'range':
list($from, $to) = is_null($value) ? [$filter['from'], $filter['to']] : explode(';', $value);
$str .= <<<HTML
<div class="row d-flex justify-content-between align-items-center mb-4">
<div class="flex-1">
<input type="text" class="form-control js-input-from" value="0" />
</div>
<div class="px-3">To</div>
<div class="flex-1">
<input type="text" class="form-control js-input-to" value="0" />
</div>
</div>
<input type="text" class="input-slider" name="{$filter['name']}" value=""
data-type="double"
data-min="{$filter['min']}"
data-max="{$filter['max']}"
data-from="{$from}"
data-to="{$to}"
data-grid="true"
/>
HTML;
break;
case 'radio':
case 'checkbox':
$totalItems = count($filter['items']);
$showMoreThreshold = 4;
foreach ($filter['items'] as $idx => $item) {
if (empty($item['value'])) continue;
$item_index = md5($filter['header'] . $item['value']);
// Buka wrapper collapse untuk item ke-5 dan seterusnya
if ($idx == $showMoreThreshold) {
$str .= '<div class="collapse" id="seeMore-' . $index . '">';
}
$filter_name = $filter['name'];
if($filter['type'] == 'checkbox') {
$filter_name .= '[' . $idx . ']';
$value = $filterArr[$filter['name'] . '[' . $idx . ']'] ?? null;
} else {
$value = $filterArr[$filter['name']] ?? null;
}
# from advanced search
if (isset($_GET[$filter['name']]) && $_GET[$filter['name']] == $item['label'])
$value = $item['value'];
$checked = $value == $item['value'] ? 'checked' : '';
$clear = ($filter['clear'] ?? false) ? 'clear="' . $filter_name . '"' : '';
$str .= <<<HTML
<div class="form-check">
<input class="form-check-input" name="{$filter_name}" type="{$filter['type']}"
id="item-{$item_index}" value="{$item['value']}" {$checked} {$clear}>
<label class="form-check-label" for="item-{$item_index}">{$item['label']}</label>
</div>
HTML;
}
// Tutup wrapper collapse jika ada item yang disimpan
if ($totalItems > $showMoreThreshold) {
$str .= '</div>';
// Menggunakan data-bs-toggle untuk Bootstrap 5
$str .= '<a class="d-block mt-2 see-more-link" href="#" data-bs-toggle="collapse" data-bs-target="#seeMore-' . $index . '" role="button" aria-expanded="false" aria-controls="seeMore-' . $index . '">';
$str .= '<span class="see-more-text">' . __('See More') . '</span>';
$str .= '<span class="see-less-text d-none">' . __('See Less') . '</span>';
$str .= '</a>';
}
break;
}
$str .= '</div></div></li>';
}
$str .= '</ul>';
$str .= '<div class="my-4"><button type="submit" class="btn btn-primary w-100">'.__('Apply Filter').'</button></div>';
# prepare to sort
$str .= '<input id="sort" name="sort" type="hidden" value="0" />';
$str .= '</form>';
// Tambahkan JavaScript untuk toggle teks "See More/See Less"
$str .= $this->getSeeMoreJavaScript();
return $str;
}
/**
* Generate JavaScript untuk toggle teks See More/See Less
*/
private function getSeeMoreJavaScript(): string
{
return <<<HTML
<script>
(function() {
// Fungsi untuk mengubah teks tombol See More/See Less
function initSeeMoreToggles() {
document.querySelectorAll('.see-more-link').forEach(function(link) {
// Hapus event listener lama jika ada
link.removeEventListener('click', handleSeeMoreClick);
// Tambahkan event listener baru
link.addEventListener('click', handleSeeMoreClick);
});
}
function handleSeeMoreClick(e) {
e.preventDefault();
const link = e.currentTarget;
const seeMoreText = link.querySelector('.see-more-text');
const seeLessText = link.querySelector('.see-less-text');
const targetId = link.getAttribute('data-bs-target');
if (targetId) {
const target = document.querySelector(targetId);
if (target) {
// Gunakan Bootstrap 5 collapse API jika tersedia
if (typeof bootstrap !== 'undefined' && bootstrap.Collapse) {
const bsCollapse = bootstrap.Collapse.getInstance(target);
if (bsCollapse) {
bsCollapse.toggle();
} else {
new bootstrap.Collapse(target, {toggle: true});
}
} else {
// Fallback manual
target.classList.toggle('show');
}
// Toggle teks setelah sedikit delay untuk memastikan class 'show' sudah berubah
setTimeout(function() {
if (target.classList.contains('show')) {
if (seeMoreText) seeMoreText.classList.add('d-none');
if (seeLessText) seeLessText.classList.remove('d-none');
} else {
if (seeMoreText) seeMoreText.classList.remove('d-none');
if (seeLessText) seeLessText.classList.add('d-none');
}
}, 100);
}
}
}
// Inisialisasi saat DOM siap
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSeeMoreToggles);
} else {
initSeeMoreToggles();
}
// Re-inisialisasi untuk filter yang dimuat secara dinamis (AJAX)
if (typeof MutationObserver !== 'undefined') {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList') {
initSeeMoreToggles();
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
})();
</script>
<style>
.see-more-link {
cursor: pointer;
text-decoration: none;
font-size: 0.875rem;
}
.see-more-link:hover {
text-decoration: underline;
}
</style>
HTML;
}
public function reOrder($filters)
{
$orderFilter = [];
$match = [];
foreach ($filters as $index => $filter) {
if (in_array($filter['header'], $match)) continue;
foreach ($filters as $checkOrderFilter) {
if (isset($checkOrderFilter['before']) && $checkOrderFilter['before'] === $filter['header']) {
$orderFilter[$index] = $checkOrderFilter;
$orderFilter[($index + 1)] = $filter;
array_push($match, $checkOrderFilter['header'], $checkOrderFilter['before']);
} elseif (isset($checkOrderFilter['after']) && $checkOrderFilter['after'] === $filter['header']) {
$orderFilter[$index] = $filter;
$orderFilter[($index + 1)] = $checkOrderFilter;
array_push($match, $checkOrderFilter['header'], $checkOrderFilter['after']);
}
}
if (!in_array($filter['header'], $match)) $orderFilter[] = $filter;
}
return $orderFilter;
}
}