Durchsuchbare Dokumentation aufrufen | Zurück zur Dokumentationsübersicht
Navigation: Dokumentationen agorum core > agorum core für Entwickler > agorum core cards > agorum core cards - Elemente
agorum.chartjs ermöglicht die Veranschaulichung von Daten, die Sie in agorum core verwalten, über die integrierte Open-Source-Bibliothek Chart.js. Sie können
| Eigenschaft | Beschreibung | Mögliche Werte |
|---|---|---|
| Allgemeine Eigenschaften | siehe Allgemeine Eigenschaften für alle Elemente | – |
| chart | Enthält die Angaben für das Diagramm. Diese bestehen im Wesentlichen aus:
|
Siehe Beispiel für die Grundstruktur eines chart-Abschnitts. Sie finden weitere Informationen zu den möglichen Werten in den Beispielen. |
Beispiel für die Grundstruktur eines chart-Abschnitts:
{
type: 'bar', // chart type
data: {
labels: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni'], // X-axis labels
datasets: [{
label: 'Beispielbeschriftung',
data: [10, 8, 12, 15, 10, 8], // Y-Achsen Werte
backgroundColor: 'rgba(75, 192, 192, 0.2)', // background color of the bars
borderColor: 'rgba(75, 192, 192, 1)', // border color of the bars
borderWidth: 1 // border width
}]
},
options: {
scales: {
y: {
beginAtZero: true // Y-axis starts at zero
}
}
}
}
Die folgenden Abschnitte zeigen beginnend mit einem sehr einfachen Beispiel, wie Sie mit Charts in agorum core arbeiten können.
Sie erstellen ein Chart in einem cardlet nach folgendem Muster.
let aguila = require('common/aguila');
// provide the data
let chartData = [];
let buildChartData = () => {
chartData = [65, 59, 80, 81, 56, 55, 40];
};
buildChartData();
// aguila widget where the chart cardlet will be displayed
let widget = aguila.create({
type: 'agorum.vbox',
background: 'dark',
width: 600,
height: 400,
items: [
{
name: 'cardView',
type: 'agorum.cards.view',
},
],
});
let sample = () => ({
type: 'agorum.card',
items: [
{
type: 'agorum.vertical',
spacing: 'm',
flex: 1,
minWidth: 100,
items: [
{
type: 'agorum.horizontal',
items: [
{
// type agorum.chartjs to indicate this is a chart cardlet
type: 'agorum.chartjs',
height: 200,
flex: 1,
name: 'chart',
// the chart information
chart: {
type: 'bar',
options: {
plugins: {
title: {
text: 'Auftragseingang pro Monat',
display: true,
},
legend: {
display: false,
},
},
options: {
scales: {
y: {
beginAtZero: true,
},
},
},
},
data: {
labels: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni'],
datasets: [
{
label: '# Auftragseingänge',
data: chartData, // use the chart data from above
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(255, 205, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(153, 102, 255, 0.2)',
],
borderColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
],
borderWidth: 1,
},
],
},
},
},
],
},
],
},
],
});
// populate the chart
widget.down('cardView').replace(sample());
widget;
In diesem Beispiel werden generierte Daten verwendet und eine Schaltfläche eingeführt, über die Anwender die Daten neu laden können. Die Zufallszahlen dienen als Beispiel für wechselnde Werte.
let aguila = require('common/aguila');
// provide the data, dynamic data - illustrated by random numbers in this example
let chartData = [];
let generateRandomNumber = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
let buildChartData = () => {
let data1 = generateRandomNumber(0, 100);
let data2 = generateRandomNumber(0, 100);
let data3 = generateRandomNumber(0, 100);
let data4 = generateRandomNumber(0, 100);
let data5 = generateRandomNumber(0, 100);
let data6 = generateRandomNumber(0, 100);
chartData = [data1, data2, data3, data4, data5, data6];
};
buildChartData();
// aguila widget where the chart cardlet will be displayed
let widget = aguila.create({
type: 'agorum.vbox',
background: 'dark',
width: 600,
height: 400,
items: [
{
// button for reloading the chart
type: 'agorum.button',
text: 'Neu laden',
name: 'redraw',
},
{
name: 'cardView',
type: 'agorum.cards.view',
},
],
});
let sample = () => ({
type: 'agorum.card',
items: [
{
type: 'agorum.vertical',
spacing: 'm',
flex: 1,
minWidth: 100,
items: [
{
type: 'agorum.horizontal',
items: [
{
// type agorum.chartjs to indicate this is a chart cardlet
type: 'agorum.chartjs',
height: 200,
flex: 1,
name: 'chart',
chart: {
type: 'bar',
options: {
plugins: {
title: {
text: 'Regenmenge in verschiedenen Jahren',
display: true,
},
legend: {
display: false,
},
},
options: {
scales: {
y: {
beginAtZero: true,
},
},
},
},
data: {
labels: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni'],
datasets: [
{
label: 'Niederschlagsmenge in Liter',
data: chartData,
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(255, 205, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(153, 102, 255, 0.2)',
],
borderColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
],
borderWidth: 1,
},
],
},
},
},
],
},
],
},
],
});
widget.down('cardView').replace(sample());
// reload the chart when the user clicks the button
widget.down('redraw').on('clicked', () => {
buildChartData();
widget.down('cardView').replace(sample());
});
// alternatively, you can auto-rebuild the chart
/*
widget.setInterval(() => {
buildChartData();
widget.down('cardView').replace(sample());
}, 1000);
*/
widget;
Sie können mehrere Charts nebeneinander anordnen und verschiedene Arten von Charts verwenden.
let aguila = require('common/aguila');
let chartData = [];
let buildChartData = () => {
chartData = [];
for (let i = 0; i < 6; i++) {
chartData.push(parseInt(Math.random() * 100));
}
};
buildChartData();
let widget = aguila.create({
type: 'agorum.vbox',
background: 'dark',
width: 1200,
height: 800,
items: [
{
name: 'cardView',
type: 'agorum.cards.view',
},
],
});
let sample = () => ({
type: 'agorum.vertical',
items: [
{
type: 'agorum.horizontal',
wrap: true,
items: [
{
type: 'agorum.card',
minWidth: 300,
flex: 1,
items: [
{
type: 'agorum.chartjs',
height: 200,
name: 'chartBar',
chart: {
type: 'bar',
options: {
plugins: {
title: {
text: 'Bar chart',
display: true,
},
legend: {
display: true,
position: 'top',
},
},
},
data: {
labels: ['Online', 'Telefon', 'Post', 'Laden 1', 'Laden 2', 'Partner'],
datasets: [
{
label: 'Anzahl Aufträge',
data: chartData,
borderWidth: 1,
backgroundColor: [
'rgba(255, 99, 132, 0.7)',
'rgba(255, 159, 64, 0.7)',
'rgba(255, 205, 86, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(54, 162, 235, 0.7)',
'rgba(153, 102, 255, 0.7)',
'rgba(201, 203, 207, 0.7)',
],
borderColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
'rgb(201, 203, 207)',
],
},
],
},
},
},
],
},
{
type: 'agorum.card',
minWidth: 300,
flex: 1,
items: [
{
type: 'agorum.chartjs',
height: 200,
name: 'chartPie',
chart: {
type: 'pie',
options: {
plugins: {
title: {
text: 'Pie chart',
display: true,
},
legend: {
display: true,
position: 'top',
},
},
},
data: {
labels: ['Online', 'Telefon', 'Post', 'Laden 1', 'Laden 2', 'Partner'],
datasets: [
{
label: 'Anzahl Aufträge',
data: chartData,
borderWidth: 1,
backgroundColor: [
'rgba(255, 99, 132, 0.7)',
'rgba(255, 159, 64, 0.7)',
'rgba(255, 205, 86, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(54, 162, 235, 0.7)',
'rgba(153, 102, 255, 0.7)',
'rgba(201, 203, 207, 0.7)',
],
borderColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
'rgb(201, 203, 207)',
],
},
],
},
},
},
],
},
{
type: 'agorum.card',
minWidth: 300,
flex: 1,
items: [
{
type: 'agorum.chartjs',
height: 200,
flex: 1,
minWidth: 300,
name: 'chartDoughnut',
chart: {
type: 'doughnut',
options: {
plugins: {
legend: {
display: true,
position: 'bottom',
},
title: {
display: true,
text: 'Doughnut chart',
},
},
},
data: {
labels: ['Online', 'Telefon', 'Post', 'Laden 1', 'Laden 2', 'Partner'],
datasets: [
{
label: 'Anzahl Aufträge',
data: chartData,
borderWidth: 1,
backgroundColor: [
'rgba(255, 99, 132, 0.7)',
'rgba(255, 159, 64, 0.7)',
'rgba(255, 205, 86, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(54, 162, 235, 0.7)',
'rgba(153, 102, 255, 0.7)',
'rgba(201, 203, 207, 0.7)',
],
borderColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
'rgb(201, 203, 207)',
],
},
],
},
},
},
],
},
{
type: 'agorum.card',
minWidth: 300,
flex: 1,
items: [
{
type: 'agorum.chartjs',
height: 200,
name: 'chart',
chart: {
type: 'bar',
options: {
indexAxis: 'y',
plugins: {
title: {
text: 'Bar chart',
display: true,
},
legend: {
display: true,
},
},
},
data: {
labels: ['Online', 'Telefon', 'Post', 'Laden 1', 'Laden 2', 'Partner'],
datasets: [
{
label: 'Anzahl Aufträge',
data: chartData,
borderWidth: 1,
backgroundColor: [
'rgba(255, 99, 132, 0.7)',
'rgba(255, 159, 64, 0.7)',
'rgba(255, 205, 86, 0.7)',
'rgba(75, 192, 192, 0.7)',
'rgba(54, 162, 235, 0.7)',
'rgba(153, 102, 255, 0.7)',
'rgba(201, 203, 207, 0.7)',
],
borderColor: [
'rgb(255, 99, 132)',
'rgb(255, 159, 64)',
'rgb(255, 205, 86)',
'rgb(75, 192, 192)',
'rgb(54, 162, 235)',
'rgb(153, 102, 255)',
'rgb(201, 203, 207)',
],
},
],
},
},
},
],
},
],
},
],
});
widget.down('cardView').replace(sample());
widget.down('cardView').on('elementClicked', name => console.log('somewhere clicked:', name));
widget;
Sie können eine Interaktion bei Klick auf einen Teil des Diagramms, etwa einen Balken, erlauben. Wenn Sie das Diagramm auf Basis von Daten in agorum core erstellt haben, können Sie beim Klick entsprechend gefilterte Suchergebnisse aufrufen.
Das folgende Beispiel zeigt folgendes:
Das Beispiel setzt voraus, dass die entsprechenden Rechnungsdokumente mit den angegebenen Metadaten existieren.
let aguila = require('common/aguila');
let objects = require('common/objects');
let time = require('common/time');
// aguila widget where the chart cardlet will be displayed
let widget = aguila.create({
type: 'agorum.vbox',
background: 'dark',
width: 600,
height: 400,
items: [
{
name: 'cardView',
type: 'agorum.cards.view',
},
],
});
// draw the chart
let drawChart = () => ({
type: 'agorum.horizontal',
wrap: true,
items: [
{
type: 'agorum.vertical',
flex: 1,
minWidth: 300,
items: [invoiceChartMonthly([])],
},
],
});
// draw monthly chart for invoices
let invoiceChartMonthly = data => {
return {
type: 'agorum.chartjs.demo.invoiceCardlet',
name: 'invoiceChartMonthly',
values: data.map(d => d.value),
labels: data.map(d => d.label),
queries: data.map(d => d.query),
barTitle: 'Einnahmen in €',
title: 'Eingangsrechnungen pro Monat in Euro',
};
};
let cardView = widget.down('cardView');
cardView.replace(drawChart([]));
// load data from existing invoices
let invoiceChartMonthlySync = widget.synchronizer();
let loadInvoicesMonthly = () => {
invoiceChartMonthlySync
.fork(() => {
let facets = objects
.query('inpath:9999 allfields:agorum_accounting_document_number')
.limit(0)
.facets({
facetData: {
type: 'range',
field: 'agorum_accounting_document_date',
start: 'NOW/MONTH-120MONTH',
end: 'NOW/MONTH+1MONTH',
gap: '+1MONTH',
other: 'all',
facet: {
value: 'sum(agorum_accounting_document_total_gross_amount)',
},
},
})
.search().facets;
let data = facets.facetData.buckets.map(bucket => ({
label: time.now().parse(bucket.val).setPattern('MM/yyyy').toString(),
value: bucket.value,
query:
'inpath:9999 allfields:agorum_accounting_document_number agorum_accounting_document_date_date_range:"' +
time.now().parse(bucket.val).setPattern('yyyy-MM').toString() +
'"',
}));
return data;
})
.then(data => {
cardView.replace('invoiceChartMonthly', invoiceChartMonthly(data));
});
};
setImmediate(() => {
loadInvoicesMonthly();
});
widget;
Sie können das zip-Paket agorum.chartjs.demo-1.0.1 verwenden, um die Chart-Funktionalität kennenzulernen. Außerdem können Sie es als Grundlage für eigene Entwicklungen einsetzen.
Gehen Sie wie folgt vor, um das zip-Paket zu installieren und die Chart-Beispiele auszuführen:
Ergebnis: Sie finden das Demo-Projekt unter Administration/customers/agorum.chartjs.demo.
| Beispiel | Beschreibung |
|---|---|
| test-chart-js.js | Dieses Beispiel zeigt, wie Sie verschiedene Arten von Charts erstellen können. Außerdem enthält es einen Redraw-Button, mit dem Sie die Daten in den Charts aktualisieren können. |
| test-chart-2.js | Dieses Beispiel enthält, verglichen mit test-chart-js.js, weitere Chart-Beispiele (Bubble, Scatter, Pie) und zeigt außerdem, wie Sie ein selbst entwickeltes Chart-cardlet verwenden können. Das verwendete chart-Cardlet chart-demo-cardlet.js finden Sie im Verzeichnis js/cardlets. |
| invoice-chart.js | Das Rechnungsbeispiel enthält ein vollständiges Anwendungsbeispiel auf Basis der Rechnungs-PDF-Dateien, die Sie mit dem Vorbereitungsskript erstellt haben. Die Charts werden durch Verwendung eines eigenen Invoice-cardlets erstellt (invoice-cardlet.js). Die ausgewerteten Daten stammen aus den Metadaten der PDF-Dateien, die mithilfe der Faceting-Funktion der agorum core Suche ausgewertet werden. Die entsprechenden Beispiele finden Sie unter den Kommentaren load data from existing invoices und load yearly data for invoices in der Datei aguila/invoice-chart.js. Das Invoice-cardlet zeigt außerdem, wie Sie Anwendern ermöglichen können, die jeweils in der Chart-Darstellung berücksichtigten Dateien durch Anklicken des Charts in der Suche zu öffnen. Den entsprechenden Aufruf von
Rechungsbeispiel mit Möglichkeit zur Anzeige der ausgewerteten Dokumente
|
| invoice-chart-search.js | Vollständiges Such-Widget für Rechnungen mit integrierter Chart-Visualisierung. Dieses Beispiel zeigt, wie Sie Chart-Visualisierungen als Suchergebnis-Widget einbinden können. Es verwendet das custom result widget invoice-chart-result.js, das sowohl Charts als auch eine normale Ergebnisliste anzeigt. Die Charts reagieren automatisch auf Änderungen der Suchabfrage. Das Beispiel zeigt außerdem: • Verwendung von |
| invoice-chart-result.js | Custom Result Widget, das Chart-Visualisierung mit klassischer Ergebnisliste kombiniert. Es zeigt monatliche und jährliche Rechnungsstatistiken an und aktualisiert sich automatisch bei Änderung der Query. Wichtiges Features dieses Beispiels: • Reaktivität: Das Widget reagiert auf |
Sie können Charts als Widget registrieren und etwa als Suchergebnis darstellen. Gehen Sie dazu wie folgt vor:
let aguila = require('common/aguila');
let filterSearchWidget = aguila.create({
type: 'agorum.composite.search.filterResultDetails',
width: 1400,
height: 800,
// filter: [ ... ],
// filterSelection: { ... },
// settings: [ ... ],
baseQuery: '*',
query: 'test',
detailsWidget: {
type: 'agorum.composite.details',
width: 600,
},
results: {
type: 'agorum.chartjs.demo.invoiceChart',
},
});
filterSearchWidget;
Sie können Charts erstellen, die automatisch auf Änderungen der Suchabfrage reagieren. Dies ist besonders nützlich, wenn Sie Charts als Teil eines Such-Widgets verwenden möchten.
Ein reaktives Result Widget definiert properties: ['query', 'selection'], um Daten vom Parent-Widget zu empfangen und zu senden. Das Widget reagiert auf das queryChanged-Event und aktualisiert sowohl die Charts als auch die Ergebnisliste.
/**
* Custom result widget for invoice chart visualization.
* Displays monthly and yearly invoice statistics using Chart.js and facet range queries.
* Reacts to query changes from parent search widget and updates charts accordingly.
* @module agorum.chartjs.demo.invoiceChartResult
*/
let aguila = require('common/aguila');
let objects = require('common/objects');
let time = require('common/time');
let i18n = require('common/i18n');
/**
* Configuration constants
*/
let INVOICE_DIRECTORY_PATH = 9999; // Invoice directory path ID
let INVOICE_HISTORY_MONTHS = 120; // 10 years of invoice history
// Create the base widget
let widget = aguila.create({
type: 'agorum.vbox',
background: 'dark',
// query is set automatically by searchResultWidget
properties: ['query', 'selection'],
items: [
{
type: 'agorum.spacer',
height: 2,
},
{
name: 'cardView',
type: 'agorum.cards.view',
},
{
type: 'agorum.composite.search.result',
name: 'result',
listType: 'detail',
flexible: true,
// init with a query for the invoice directory, will be updated when query changes
// query: 'inpath:' + INVOICE_DIRECTORY_PATH,
query: INVOICE_DIRECTORY_PATH,
sort: [
{
property: 'lastModifyDate',
direction: 'DESC',
},
],
},
],
});
/**
* Creates the chart container layout with monthly and yearly charts.
* @returns {Object} Widget configuration object with horizontal layout
*/
let drawChart = () => ({
type: 'agorum.horizontal',
wrap: true,
items: [
{
type: 'agorum.vertical',
flex: 1,
minWidth: 300,
items: [invoiceChartMonthly([])],
},
{
type: 'agorum.vertical',
flex: 1,
minWidth: 300,
items: [invoiceChartYearly([])],
},
],
});
/**
* Creates monthly invoice chart configuration.
* @param {Array} data - Array of data objects with properties: value, label, query
* @returns {Object} Chart widget configuration for monthly view with borderless display
*/
let invoiceChartMonthly = data => {
return {
type: 'agorum.chartjs.demo.invoiceCardlet',
borderless: true, // Remove border for seamless display
name: 'invoiceChartMonthly',
values: data.map(d => d.value),
labels: data.map(d => d.label),
queries: data.map(d => d.query),
barTitle: i18n.translate('agorum.chartjs.demo.invoices.monthly.barTitle'),
title: i18n.translate('agorum.chartjs.demo.invoices.monthly.title'),
};
};
/**
* Creates yearly invoice chart configuration.
* @param {Array} data - Array of data objects with properties: value, label, query
* @returns {Object} Chart widget configuration for yearly view with borderless display
*/
let invoiceChartYearly = data => {
return {
type: 'agorum.chartjs.demo.invoiceCardlet',
borderless: true, // Remove border for seamless display
name: 'invoiceChartYearly',
values: data.map(d => d.value),
labels: data.map(d => d.label),
queries: data.map(d => d.query),
barTitle: i18n.translate('agorum.chartjs.demo.invoices.yearly.barTitle'),
title: i18n.translate('agorum.chartjs.demo.invoices.yearly.title'),
};
};
let cardView = widget.down('cardView');
cardView.replace(drawChart([]));
/**
* Loads monthly invoice data using facet range queries.
* Uses synchronizer to handle async operations and update the monthly chart.
* Queries invoices from the last 10 years grouped by month.
*/
let invoiceChartMonthlySync = widget.synchronizer();
let loadInvoicesMonthly = () => {
let query = widget.query;
if (!query) return;
invoiceChartMonthlySync
.fork(() => {
let facets = objects
.query(query)
.limit(0)
.facets({
facetData: {
type: 'range',
field: 'agorum_accounting_document_date',
start: 'NOW/MONTH-' + INVOICE_HISTORY_MONTHS + 'MONTH',
end: 'NOW/MONTH+1MONTH',
gap: '+1MONTH',
other: 'all',
facet: {
value: 'sum(agorum_accounting_document_total_gross_amount)',
},
},
})
.search().facets;
let data = facets.facetData.buckets.map(bucket => ({
label: time.now().parse(bucket.val).setPattern('MM/yyyy').toString(),
value: bucket.value,
// Search in configured invoice directory for documents in this month
query:
'inpath:' +
INVOICE_DIRECTORY_PATH +
' allfields:agorum_accounting_document_number agorum_accounting_document_date_date_range:"' +
time.now().parse(bucket.val).setPattern('yyyy-MM').toString() +
'"',
}));
return data;
})
.then(data => {
cardView.replace('invoiceChartMonthly', invoiceChartMonthly(data));
})
.catch(error => {
console.log('Error loading monthly invoice data: ' + error.message);
if (error.stack) {
console.log(error.stack);
}
});
};
/**
* Loads yearly invoice data using facet range queries.
* Uses synchronizer to handle async operations and update the yearly chart.
* Queries invoices from the last 10 years grouped by year.
*/
let invoiceChartYearlySync = widget.synchronizer();
let loadInvoicesYearly = () => {
let query = widget.query;
if (!query) return;
invoiceChartYearlySync
.fork(() => {
let facets = objects
.query(query)
.limit(0)
.facets({
facetData: {
type: 'range',
field: 'agorum_accounting_document_date',
start: 'NOW/YEAR-' + INVOICE_HISTORY_MONTHS + 'MONTH',
end: 'NOW/YEAR+1MONTH',
gap: '+1YEAR',
other: 'all',
facet: {
value: 'sum(agorum_accounting_document_total_gross_amount)',
},
},
})
.search().facets;
let data = facets.facetData.buckets.map(bucket => ({
label: time.now().parse(bucket.val).setPattern('yyyy').toString(),
value: bucket.value,
// Search in configured invoice directory for documents in this year
query:
'inpath:' +
INVOICE_DIRECTORY_PATH +
' allfields:agorum_accounting_document_number agorum_accounting_document_date_date_range:"' +
time.now().parse(bucket.val).setPattern('yyyy').toString() +
'"',
}));
return data;
})
.then(data => {
cardView.replace('invoiceChartYearly', invoiceChartYearly(data));
})
.catch(error => {
console.log('Error loading yearly invoice data: ' + error.message);
if (error.stack) {
console.log(error.stack);
}
});
};
let result = widget.down('result');
/**
* Event handler for selection changes in the result list.
* Propagates selection changes from the result widget to the parent widget.
* @param {Array} selection - Array of selected object IDs
*/
result.on('selectionChanged', selection => {
widget.selection = selection;
});
/**
* Event handler for query changes.
* When the search query changes, reload both monthly and yearly charts
* and update the result list widget.
*/
widget.on('queryChanged', query => {
if (!query) return;
// Reload chart data
loadInvoicesMonthly();
loadInvoicesYearly();
// Update result list if widget exists
if (result) {
result.query = query;
}
});
widget;
Wichtige Aspekte bei reaktiven Widgets:
properties: ['query', 'selection'] ermöglicht die Kommunikation mit dem Parent-Widgetwidget.on('queryChanged', ...) reagiert auf Änderungen der Suchabfrageif (!query) return; verhindert Fehler bei leeren Queries.catch() fängt Fehler bei asynchronen Operationen ab