{{ t.pages.datatable.overviewIntro }}
{{ t.common.sectionDescription }}
{{ t.pages.datatable.descriptionBody }}
{{ t.common.sectionFeatures }}
- {{ t.pages.datatable.featureOne }}
- {{ t.pages.datatable.featureTwo }}
- {{ t.pages.datatable.featureThree }}
- {{ t.pages.datatable.featureFour }}
- {{ t.pages.datatable.featureFive }}
{{ t.common.sectionLimitations }}
- {{ t.pages.datatable.limitOne }}
- {{ t.pages.datatable.limitTwo }}
- {{ t.pages.datatable.limitThree }}
<li-datatable
[dataTableFilter]="filters"
[data]="tableData"
[settings]="tableSettings"
[searchInFields]="searchFields"
[responsiveCollapse]="true"
(dataRequest)="onTableRequest($event)">
</li-datatable>
{{ t.pages.datatable.demoIntro }}
{{ t.pages.datatable.onDemandTitle }}
{{ t.pages.datatable.onDemandIntro }}
{{ t.pages.datatable.lazyModalTitle }}
{{ t.pages.datatable.lazyModalIntro }}
{{ t.pages.datatable.modalBody }}
{{ t.common.sectionHowToUse }}
{{ t.pages.datatable.howToUseBody }}
{{ t.common.sectionMainOptions }}
- {{ t.pages.datatable.optionOne }}
- {{ t.pages.datatable.optionTwo }}
- {{ t.pages.datatable.optionThree }}
- {{ t.pages.datatable.optionFour }}
{{ t.common.sectionBestPractices }}
- {{ t.pages.datatable.practiceOne }}
- {{ t.pages.datatable.practiceTwo }}
- {{ t.pages.datatable.practiceThree }}
{{ t.pages.datatable.columnStylesTitle }}
- {{ t.pages.datatable.columnStyleOne }}
- {{ t.pages.datatable.columnStyleTwo }}
- {{ t.pages.datatable.columnStyleThree }}
- {{ t.pages.datatable.columnStyleFour }}
- {{ t.pages.datatable.columnStyleFive }}
DatatableCol(
key: 'status',
title: 'Status',
width: '160px',
textAlign: 'center',
nowrap: true,
cellStyleResolver: (itemMap, itemInstance) {
final status = itemMap['status']?.toString() ?? '';
return status == 'Bloqueado'
? 'color: #b91c1c; font-weight: 700;'
: 'color: #0f766e; font-weight: 700;';
},
)
RowStyleResolver e grid
rowStyleResolverpermite destacar uma linha inteira sem alterar o template.gridTemplateColumnsdefine a malha do grid.gridGapcontrola o espaçamento entre cartões.customCardBuilderrecebeitemMap,itemInstanceerowpara montar umElementcompleto.
DatatableSettings(
colsDefinitions: cols,
rowStyleResolver: (itemMap, itemInstance) {
if (itemMap['health'] == 'Crítica') {
return 'background-color: rgba(239, 68, 68, 0.08);';
}
return null;
},
gridTemplateColumns: 'repeat(auto-fit, minmax(240px, 1fr))',
gridGap: '1rem',
customCardBuilder: (itemMap, itemInstance, row) {
final root = DivElement()..classes.add('my-card');
root.text = itemMap['feature']?.toString() ?? '';
return root;
},
)
Fluxo mínimo full stack com Product
Abaixo está um fluxo mínimo, baseado no padrão real de backend e frontend deste repositório. Os snippets ficam estáticos no próprio template para evitar sobrecarga dinâmica.
1. Model Product
Estrutura mínima seguindo o padrão de models do core com
SerializeBase.
import 'serialize_base.dart';
class Product implements SerializeBase {
static const tableName = 'products';
static const fqtn = 'public.$tableName';
static const idCol = 'id';
static const nameCol = 'name';
static const priceCol = 'price';
static const statusCol = 'status';
int id;
String name;
double price;
String status;
Product({
required this.id,
required this.name,
required this.price,
required this.status,
});
@override
Map<String, dynamic> toMap() {
return {
idCol: id,
nameCol: name,
priceCol: price,
statusCol: status,
};
}
Map<String, dynamic> toInsertMap() => toMap()..remove(idCol);
Map<String, dynamic> toUpdateMap() => toMap()..remove(idCol);
factory Product.fromMap(Map<String, dynamic> map) {
return Product(
id: map[idCol] as int,
name: map[nameCol] as String,
price: (map[priceCol] as num).toDouble(),
status: map[statusCol] as String,
);
}
}
2. Backend com Eloquent + DataFrame
O repositório consulta a tabela, aplica filtros, paginação e ordenação, e
retorna um DataFrame pronto para serialização.
class ProductRepository {
final Connection db;
ProductRepository(this.db);
Future<DataFrame<Map<String, dynamic>>> list({Filters? filtros}) async {
final query = db.table(Product.fqtn);
query.selectRaw('*');
if (filtros?.isSearch == true) {
final search = '%${filtros!.searchString!.toLowerCase()}%';
query.whereRaw('unaccent(name) ilike unaccent(?)', [search]);
}
final totalRecords = await query.count();
if (filtros?.isOrder == true) {
query.orderBy(filtros!.orderBy!, filtros.orderDir!);
} else {
query.orderBy(Product.nameCol, 'asc');
}
if (filtros?.isLimit == true) {
query.limit(filtros!.limit!);
}
if (filtros?.isOffset == true) {
query.offset(filtros!.offset!);
}
final rows = await query.get();
return DataFrame<Map<String, dynamic>>(
items: rows,
totalRecords: totalRecords,
);
}
}
class ProductController {
static Future<Response> list(Request req) async {
final filtros = Filters.fromMap(req.url.queryParameters);
final repo = req.make<ProductRepository>();
final data = await repo.list(filtros: filtros);
return responseDataFrame(data);
}
}
3. Service no frontend
O service consome a rota e transforma o JSON em
DataFrame<Product> usando o builder.
class ProductService extends RestServiceBase {
ProductService(RestConfig conf) : super(conf);
final String path = '/products';
Future<DataFrame<Product>> list(Filters filtros) async {
return getDataFrame<Product>(
path,
builder: Product.fromMap,
filtros: filtros,
);
}
}
4. Page AngularDart
A page mantém Filters, DatatableSettings e
chama o service sempre que o datatable emite dataRequest.
class ListaProdutoPage implements OnActivate {
ListaProdutoPage(this.hostElement, this.productService);
final Element hostElement;
final ProductService productService;
final filtro = Filters(limit: 12, offset: 0);
DataFrame<Product> items = DataFrame<Product>.newClear();
final DatatableSettings dtSettings = DatatableSettings(
colsDefinitions: [
DatatableCol(key: 'id', title: 'Id', sortingBy: 'id', enableSorting: true),
DatatableCol(key: 'name', title: 'Nome', sortingBy: 'name', enableSorting: true),
DatatableCol(key: 'price', title: 'Preço'),
DatatableCol(key: 'status', title: 'Status', hideOnMobile: true),
],
);
final List<DatatableSearchField> sInFields = <DatatableSearchField>[
DatatableSearchField(field: 'name', operator: 'like', label: 'Nome'),
DatatableSearchField(field: 'status', operator: '=', label: 'Status'),
];
Future<void> load() async {
final loading = SimpleLoading();
try {
loading.show(target: hostElement);
items = await productService.list(filtro);
} finally {
loading.hide();
}
}
}
5. Template com li-datatable
No template, o datatable recebe o filtro, os dados e as definições de coluna, reproduzindo o padrão das páginas reais do frontend.
<div class="card">
<li-datatable
[dataTableFilter]="filtro"
[data]="items"
[settings]="dtSettings"
[searchInFields]="sInFields"
(dataRequest)="onDtRequestData($event)">
</li-datatable>
</div>