A termék-lista gridje rendezhető és szűrhető lett
Alapból csak az aktív termékek jönnek: a katalógusban ~600 termék van, ebből egyszerre ~70 él, a többi archív. A nop kereső-paneljének Publikált legördülője eddig „Mind"-en állt — most csak publikált az alapérték, és a legördülőn bármikor átkapcsolható. - Ordering = true (eddig ki volt kommentelve) és ServerSide = false. A kliens-oldali mód nem kényelmi döntés: a saját oszlopok (tára, átlagsúly, mérendő, kamionon, elérhető) a lapozott találatra UTÓLAG kerülnek rá a ProductDto-ból, tehát nincsenek a SQL-ben — szerver-oldalon csak oldalanként lehetne rendezni őket, ami helyesnek látszik és téved - a ProductList emiatt nem lapoz: a BaseSearchModel alapértéke 10 sor, és a DataTables kliens-módban nem küld lapozást — beavatkozás nélkül 10 sort hinne az egésznek - ha a lista mégis elérné a felső korlátot, a grid kiírja; enélkül a rendezés és a szűrés némán a katalógus egy részén dolgozna - gyorsszűrő sor: Név, SKU (szöveg), Elérhető (van/nincs), Mérendő?, Publikált - alapból 100 sor/oldal, a menüben is szerepel Két csapda a nop grid körül, mindkettő beírva a kódba: - scrollX mellett a DataTables KLÓNOZZA a fejlécet, és a jQuery kezelők a klónra nem másolódnak át. Ezért a sor a törzs-tábla fejlécébe épül (azt klónozza újra minden rajzoláskor), az események pedig delegáltak, data-fb-col alapján - a kereséshez a DataTables kiszedi a HTML-t a cellákból, tehát a nop ikonra renderelt logikai oszlopokra a kirenderelt osztálynévvel keresni SOSEM talál — üres eredményt ad, ami hibátlan szűrőnek látszik. Minden nem-szöveges szűrő ezért a nyers sor-objektumot nézi
This commit is contained in:
@@ -880,11 +880,29 @@ public partial class CustomProductController : BaseAdminController
|
||||
[CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
|
||||
public virtual async Task<IActionResult> List()
|
||||
{
|
||||
var model = await _productModelFactory.PrepareProductSearchModelAsync(new ProductSearchModel());
|
||||
// Alapból csak az AKTÍV termékek. A katalógusban ~600 termék van, ebből egyszerre ~70 él; a többi
|
||||
// archív, és a listát használhatatlanná tenné. A nop konvenciója: 0 = mind, 1 = csak publikált,
|
||||
// 2 = csak nem publikált — a kereső-panel legördülőjén az admin bármikor átállíthatja.
|
||||
var model = await _productModelFactory.PrepareProductSearchModelAsync(
|
||||
new ProductSearchModel { SearchPublishedId = PublishedOnlySearchId });
|
||||
|
||||
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/Product/List.cshtml", model);
|
||||
}
|
||||
|
||||
/// <summary>A nop „csak publikált" szűrő-értéke a termék-kereső legördülőjében.</summary>
|
||||
private const int PublishedOnlySearchId = 1;
|
||||
|
||||
/// <summary>
|
||||
/// A kliens-oldali termék-grid felső sorkorlátja.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A grid a böngészőben rendez és szűr (<c>List.cshtml</c> → <c>ServerSide = false</c>), ezért EGYBEN kell
|
||||
/// megkapnia a találatokat. A <see cref="BaseSearchModel"/> alapértéke 10 sor, és a DataTables kliens-módban
|
||||
/// nem küld lapozást — beavatkozás nélkül tehát 10 sort hinne az egésznek, a rendezés pedig némán csak azon
|
||||
/// belül működne. A korlát bőséges a ~600-as katalógushoz; ha valaha túlnő rajta, a nézet ki is írja.
|
||||
/// </remarks>
|
||||
private const int ProductGridMaxRows = 5000;
|
||||
|
||||
[CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
|
||||
public virtual async Task<IActionResult> BulkEdit()
|
||||
{
|
||||
@@ -918,6 +936,10 @@ public partial class CustomProductController : BaseAdminController
|
||||
[CheckPermission(StandardPermission.Catalog.PRODUCTS_VIEW)]
|
||||
public virtual async Task<IActionResult> ProductList(ProductSearchModel searchModel)
|
||||
{
|
||||
// A kliens-oldali grid nem lapoz a szerveren — lásd ProductGridMaxRows.
|
||||
searchModel.Start = 0;
|
||||
searchModel.Length = ProductGridMaxRows;
|
||||
|
||||
//prepare model
|
||||
var model = await _productModelFactory.PrepareProductListModelExtendedAsync(searchModel);
|
||||
|
||||
|
||||
@@ -218,10 +218,18 @@
|
||||
Name = "products-grid",
|
||||
UrlRead = new DataUrl("ProductList", "CustomProduct", null),
|
||||
SearchButtonId = "search-products",
|
||||
Length = Model.PageSize,
|
||||
// Ordering = true,
|
||||
// ServerSide = false,
|
||||
LengthMenu = Model.AvailablePageSizes,
|
||||
// Alapból 100 sor: ~70 aktív termék van, tehát egy lapon elfér az egész napi munka.
|
||||
Length = 100,
|
||||
// Rendezés ÉS oszlop-szűrés a böngészőben. A saját oszlopok (tára, átlagsúly,
|
||||
// mérendő, kamionon, elérhető) a lapozott találatra UTÓLAG kerülnek rá
|
||||
// (CustomProductModelFactory), tehát nincsenek benne a SQL-ben — szerver-oldalon
|
||||
// rendezni őket csak oldalanként lehetne, ami helyesnek látszik és téved.
|
||||
// Kliens-oldalon viszont mind rendezhető és szűrhető, egyformán.
|
||||
Ordering = true,
|
||||
ServerSide = false,
|
||||
// A menüben szerepelnie KELL a 100-nak, különben a DataTables az első menüpontra esne
|
||||
// vissza, és a fenti alapérték néma módon nem érvényesülne.
|
||||
LengthMenu = "20,50,100,200,500",
|
||||
Filters = new List<FilterParameter>
|
||||
{
|
||||
new FilterParameter(nameof(Model.SearchProductName)),
|
||||
@@ -349,6 +357,182 @@
|
||||
});
|
||||
|
||||
</script>
|
||||
<script>
|
||||
/* ── Gyorsszűrő sor a termék-gridhez ──────────────────────────────────────
|
||||
|
||||
Két dolgot kell kikerülnie, és mindkettő megharapott minket:
|
||||
|
||||
1) A grid `scrollX: true`-val megy, ezért a DataTables a fejlécet KLÓNOZZA egy külön,
|
||||
görgethető táblába, és minden rajzoláskor újraklónozza. Az elemekre kötött jQuery
|
||||
eseménykezelők a klónra NEM másolódnak át. Ezért a sor a törzs-tábla fejlécébe
|
||||
épül (azt klónozza a DataTables, tehát magától visszaépül), az események pedig
|
||||
DELEGÁLTAK, `data-fb-col` attribútum alapján.
|
||||
|
||||
2) A DataTables a kereséshez KISZEDI a HTML-t a cellákból. A logikai oszlopokat a nop
|
||||
ikonra rendereli (`<i class="fas fa-check true-icon">`), tehát a kirenderelt
|
||||
osztálynévre keresni sosem talál — üres eredményt ad, ami hibátlan szűrőnek látszik.
|
||||
Ezért minden nem-szöveges szűrő a NYERS sor-objektumot nézi (`ext.search` 4.
|
||||
paramétere), nem a megjelenítést.
|
||||
|
||||
A definíciók az OSZLOP ADATNEVÉRE kulcsolnak, nem a sorszámára: az `Id` háromszor is
|
||||
oszlop (kijelölő, azonosító, szerkesztés-gomb), és a rejtett kép-oszlop miatt a
|
||||
pozíciók amúgy is elcsúsznának. */
|
||||
(function () {
|
||||
var GRID = "products-grid";
|
||||
|
||||
/* Oszlop adatneve → a beírt szűrő-érték. A klónok ebből épülnek vissza. */
|
||||
var state = {};
|
||||
|
||||
/* Adatnév → oszlop-index. A nop NEM ad `name` propertyt az oszlopoknak, ezért a
|
||||
`"Name:name"` szelektor nem oldódna fel — némán nem szűrne semmit. */
|
||||
var colIndex = {};
|
||||
|
||||
var yesNo = [["", "Mind"], ["true", "Igen"], ["false", "Nem"]];
|
||||
|
||||
var defs = {
|
||||
"Name" : { kind: "text" },
|
||||
"Sku" : { kind: "text" },
|
||||
/* Az „elérhető" MENNYISÉG: a részszó-keresés félrevezetne (a „10" a 100-ra és a
|
||||
210-re is illeszkedne), ezért van/nincs bontás, számként összehasonlítva. */
|
||||
"AvailableQuantity": { kind: "qty", opts: [["", "Mind"], ["pos", "Van (> 0)"], ["zero", "Nincs (0)"]] },
|
||||
"IsMeasurable" : { kind: "bool", opts: yesNo },
|
||||
"Published" : { kind: "bool", opts: yesNo }
|
||||
};
|
||||
|
||||
/* A nyers soron dolgozó szűrők. Az ext.search GLOBÁLIS: minden gridre lefut, ezért a
|
||||
sajátunkra kell szűkíteni. A 4. paraméter a JSON-ból jött sor-objektum — pont az, amit
|
||||
a HTML-kiszedés nem érint. */
|
||||
$.fn.dataTable.ext.search.push(function (settings, searchData, dataIndex, row) {
|
||||
if (settings.nTable.id !== GRID || !row) return true;
|
||||
|
||||
for (var key in defs) {
|
||||
var def = defs[key];
|
||||
var want = state[key];
|
||||
if (def.kind === "text" || !want) continue;
|
||||
|
||||
if (def.kind === "bool") {
|
||||
if (String(row[key]) !== want) return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
var value = parseFloat(row[key]);
|
||||
if (isNaN(value)) value = 0;
|
||||
|
||||
if (want === "pos" && !(value > 0)) return false;
|
||||
if (want === "zero" && value > 0) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
function buildRow(api, $header) {
|
||||
var $row = $("<tr class='fb-filter-row'></tr>");
|
||||
|
||||
api.columns().every(function (idx) {
|
||||
/* A rejtett kép-oszlopnak fejléc-cellája sincs — egy fölösleges th elcsúsztatná a sort. */
|
||||
if (!this.visible()) return;
|
||||
|
||||
var key = this.dataSrc();
|
||||
colIndex[key] = idx;
|
||||
|
||||
var $th = $("<th></th>").appendTo($row);
|
||||
var def = defs[key];
|
||||
if (!def) return;
|
||||
|
||||
var value = state[key] || "";
|
||||
var $field;
|
||||
|
||||
if (def.kind === "text") {
|
||||
$field = $("<input type='text' class='form-control form-control-sm'>")
|
||||
.attr("placeholder", "szűrés…").attr("value", value);
|
||||
} else {
|
||||
$field = $("<select class='form-control form-control-sm'></select>");
|
||||
|
||||
def.opts.forEach(function (o) {
|
||||
var $o = $("<option>").val(o[0]).text(o[1]);
|
||||
/* selected ATTRIBÚTUM, nem property: a klón csak az attribútumot viszi magával. */
|
||||
if (o[0] === value) $o.attr("selected", "selected");
|
||||
$o.appendTo($field);
|
||||
});
|
||||
}
|
||||
|
||||
$field.attr("data-fb-col", key).attr("data-fb-kind", def.kind).val(value).appendTo($th);
|
||||
});
|
||||
|
||||
/* A TÖRZS-tábla fejlécébe kötünk: scrollX mellett ezt klónozza a DataTables a látható
|
||||
görgető-fejlécbe, minden rajzoláskor újra — így a sor magától visszaépül. Közvetlenül
|
||||
a klónba írni hasztalan: a következő rajzolás felülírja. */
|
||||
$header.append($row);
|
||||
}
|
||||
|
||||
$(document).on("init.dt", function (e, settings) {
|
||||
if (settings.nTable.id !== GRID) return;
|
||||
buildRow(new $.fn.dataTable.Api(settings), $(settings.nTable).find("thead").first());
|
||||
});
|
||||
|
||||
function syncCopies(selector, key, value) {
|
||||
$(selector + "[data-fb-col='" + key + "']").each(function () {
|
||||
if (this.value !== value) this.value = value;
|
||||
|
||||
if (this.tagName === "SELECT") {
|
||||
$(this).find("option").each(function () {
|
||||
$(this).attr("selected", this.value === value ? "selected" : null);
|
||||
});
|
||||
} else {
|
||||
$(this).attr("value", value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Delegálva a dokumentumra: a klónok bármikor keletkezhetnek, kezelőt nem kapnak. */
|
||||
var timer;
|
||||
|
||||
$(document).on("input", "input[data-fb-col]", function () {
|
||||
var key = $(this).attr("data-fb-col");
|
||||
var value = this.value;
|
||||
|
||||
state[key] = value;
|
||||
syncCopies("input", key, value);
|
||||
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(function () {
|
||||
var api = $("#" + GRID).DataTable();
|
||||
api.column(colIndex[key]).search(value);
|
||||
api.draw();
|
||||
}, 300);
|
||||
});
|
||||
|
||||
$(document).on("change", "select[data-fb-col]", function () {
|
||||
var key = $(this).attr("data-fb-col");
|
||||
|
||||
state[key] = this.value;
|
||||
syncCopies("select", key, this.value);
|
||||
|
||||
/* A logikai és a mennyiségi szűrő az ext.search-ben fut, ezért itt elég újrarajzolni. */
|
||||
$("#" + GRID).DataTable().draw();
|
||||
});
|
||||
|
||||
/* A kliens-oldali mód a kapott sorokat tekinti a teljes halmaznak. Ha a szerver a felső
|
||||
korlátnál (ProductGridMaxRows) elvágta a listát, azt LÁTNI kell — különben a rendezés
|
||||
és a szűrés némán a katalógus egy részén dolgozna.
|
||||
A nop `dataSrc: "Data"`-val ad vissza, tehát a mező NAGY kezdőbetűs. */
|
||||
$(document).on("xhr.dt", function (e, settings, json) {
|
||||
if (settings.nTable.id !== GRID || !json || !json.recordsTotal) return;
|
||||
|
||||
var got = (json.Data || []).length;
|
||||
if (json.recordsTotal <= got) return;
|
||||
|
||||
$("#" + GRID + "_wrapper").prepend(
|
||||
"<div class='alert alert-warning py-2 mb-2'>A lista csonkolva: " + got + " / " +
|
||||
json.recordsTotal + " termék jött le. A rendezés és a szűrés csak a letöltött részen " +
|
||||
"dolgozik — szűkíts a fenti kereső-panellel.</div>");
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
|
||||
<nop-alert asp-alert-id="deleteSelectedFailed" />
|
||||
<nop-alert asp-alert-id="nothingSelectedAlert" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user