This commit is contained in:
2026-05-21 13:26:33 +02:00
parent 9b5f12bc94
commit 257339911e
6 changed files with 213 additions and 2 deletions

View File

@@ -997,6 +997,17 @@ Point de deploiement:
- test obligatoire apres toute retouche layout/scroll: ouvrir une forme Purchase sans AG Grid principale, menu gauche ouvert puis cache; verifier que le formulaire descend jusqu'aux onglets/lignes et que la zone centrale se redimensionne correctement
- test complementaire: ouvrir Lots Management ou une vue report AG Grid avec filtres hauts; verifier que le scroll vertical utile reste celui de l'AG Grid et que le conteneur SAO ne cree pas un deuxieme scroll parasite
### Selection globale AG Grid
- demande UX: retrouver la tick d'en-tete de l'ancien tree SAO pour selectionner/deselectionner toutes les lignes visibles de la AG Grid
- implementation: colonne speciale `__selection__` avec un `headerComponent` custom et une checkbox d'en-tete
- comportement retenu: la tick agit sur les vrais nodes `record` apres filtre AG Grid, et ignore explicitement les lignes synthetiques `group` / `total`
- la checkbox d'en-tete passe en `indeterminate` quand une partie seulement des lignes visibles est selectionnee
- synchronisation: apres un changement de selection, un filtre ou un rebuild de `rowData`, appeler `_sync_selection_header_checkbox()` pour garder l'etat visuel coherent avec `_selected_ids`
- subtilite AG Grid importante: ne pas passer l'instance `Sao.View.AGGridTree` dans `headerComponentParams` ou `filterParams`; l'objet contient des references circulaires et AG Grid peut partir en `too much recursion` en traitant les `columnDefs`
- correctif retenu: creer le composant d'en-tete via une closure (`_get_selection_header_component`) plutot qu'en passant l'instance SAO complete dans les params
- invariant: dans les params AG Grid, passer seulement des primitives, des tableaux/objets simples ou de petites fonctions ciblees; eviter les gros objets SAO (`screen`, `tree`, `view`, widgets jQuery)
### Commits importants
- `fc9ab5d Save SAO AG Grid work`

View File

@@ -323,12 +323,14 @@ Traces a lire si le sujet revient:
- resize colonnes: ne pas lancer le refresh layout global depuis `onColumnResized`; annuler les timers pendant le drag et sauver la largeur avec `skipLayoutRefresh`, sinon `doLayout()` peut annuler la largeur choisie par l'utilisateur
- resize colonnes: `_force_horizontal_scroll_width()` ne doit jamais appeler `setColumnWidth()`; calculer le scroll avec `getActualWidth()` pour respecter les largeurs utilisateur
- futur tri en mode grouping: autoriser asc/desc/none et le memoriser dans la config de vue, mais ne pas reactiver le tri natif AG Grid sur le `rowData` groupe; capturer l'intention, trier les records source avant `_flatten_grouped_rows()`, puis reconstruire les lignes `group`/`record`/`total` en preservant expansions, totaux et filtres checklist actifs
- composants custom AG Grid: ne jamais passer l'instance SAO complete (`tree`, `screen`, `view`, widgets jQuery) dans `headerComponentParams` ou `filterParams`; ces objets sont circulaires et peuvent provoquer `too much recursion` pendant le traitement des `columnDefs`. Preferer une closure ou de petites fonctions ciblees.
### AG Grid - règles d'or
- lire d'abord `AG_GRID_PROGRESS.md > Synthese fin de session AG Grid` avant de toucher aux filtres, vues, rowData, layout ou resize
- ne pas confondre: largeur par defaut, largeur minimale, largeur reelle utilisateur, largeur scrollable
- ne pas confondre: filtre AG Grid natif, checklist UI, `_sao_value_filters`, `valueFilters` persiste, `rowData` prefiltre
- ne pas passer de gros objets SAO circulaires dans les params AG Grid; preferer closures, primitives et fonctions explicites
- toute correction de stabilisation doit etre testee contre une action utilisateur inverse: clear filtre, elargir filtre, reduire colonne, revenir a `Default view`
- si une correction semble logique mais ne marche pas, ajouter une trace de transition avant/apres la fonction suspecte; c'est comme ca que `_force_horizontal_scroll_width()` a ete identifie
- traces temporaires a supprimer/reduire apres validation finale: `[AGGRID][COLUMN_RESIZE_JSON]` et les logs lifecycle/value filter devenus trop bruyants

5
dist/tryton-sao.css vendored
View File

@@ -11761,6 +11761,11 @@ html[theme="default"] .ag-grid-tree-grouping-panel input[type="checkbox"]:after
padding-left: 6px;
}
.ag-grid-tree-host .ag-header-cell[col-id="__selection__"] .ag-header-cell-comp-wrapper {
justify-content: center;
padding-left: 0;
}
.ag-grid-tree-host .ag-selection-checkbox,
.ag-grid-tree-host .ag-header-select-all {
display: flex;

96
dist/tryton-sao.js vendored
View File

@@ -24613,7 +24613,10 @@ function eval_pyson(value){
onRowDoubleClicked: this._on_row_double_clicked.bind(this),
onSelectionChanged: this._on_selection_changed.bind(this),
onSortChanged: this._on_sort_changed.bind(this),
onFilterChanged: this._schedule_state_save.bind(this),
onFilterChanged: () => {
this._schedule_state_save();
this._sync_selection_header_checkbox();
},
onColumnMoved: this._on_grid_column_layout_changed.bind(this),
onColumnPinned: this._on_grid_column_layout_changed.bind(this),
onColumnResized: this._on_grid_column_resized.bind(this),
@@ -24715,6 +24718,7 @@ function eval_pyson(value){
sortable: false,
resizable: false,
filter: false,
headerComponent: this._get_selection_header_component(),
cellRenderer: this._create_selection_cell_renderer.bind(this),
});
}
@@ -24878,6 +24882,94 @@ function eval_pyson(value){
}
return checkbox;
},
_get_selection_header_component: function() {
var tree = this;
var SelectionHeader = function() {};
SelectionHeader.prototype.init = function() {
this.gui = tree._create_selection_header_renderer();
};
SelectionHeader.prototype.getGui = function() {
return this.gui;
};
SelectionHeader.prototype.destroy = function() {
if (tree._selection_header_checkbox === this.gui) {
tree._selection_header_checkbox = null;
}
this.gui = null;
};
return SelectionHeader;
},
_create_selection_header_renderer: function() {
if (this.selection_mode != Sao.common.SELECTION_MULTIPLE) {
return document.createTextNode('');
}
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'ag-custom-checkbox ag-custom-header-checkbox';
checkbox.title = Sao.i18n.gettext('Select all');
checkbox.setAttribute('aria-label', Sao.i18n.gettext('Select all'));
checkbox.addEventListener('click', event => {
event.stopPropagation();
});
checkbox.addEventListener('change', event => {
event.stopPropagation();
this._set_visible_records_selection(checkbox.checked);
});
this._selection_header_checkbox = checkbox;
window.requestAnimationFrame(() => {
this._sync_selection_header_checkbox();
});
return checkbox;
},
_get_selectable_grid_nodes: function() {
var nodes = [];
if (!this.gridApi) {
return nodes;
}
var collect = node => {
if (node && node.data && node.data.__kind == 'record' &&
node.data.__record) {
nodes.push(node);
}
};
if (this.gridApi.forEachNodeAfterFilter) {
this.gridApi.forEachNodeAfterFilter(collect);
} else {
this.gridApi.forEachNode(collect);
}
return nodes;
},
_set_visible_records_selection: function(value) {
if (this.selection_mode != Sao.common.SELECTION_MULTIPLE ||
!this.gridApi) {
return;
}
var nodes = this._get_selectable_grid_nodes();
this._syncing_grid = true;
try {
nodes.forEach(node => {
node.setSelected(Boolean(value));
});
} finally {
this._syncing_grid = false;
}
this._on_selection_changed();
this._sync_selection_header_checkbox();
},
_sync_selection_header_checkbox: function() {
var checkbox = this._selection_header_checkbox;
if (!checkbox ||
this.selection_mode != Sao.common.SELECTION_MULTIPLE) {
return;
}
var nodes = this._get_selectable_grid_nodes();
var selected_count = nodes.filter(node => node.isSelected()).length;
checkbox.disabled = !nodes.length;
checkbox.checked = Boolean(
nodes.length && selected_count == nodes.length);
checkbox.indeterminate = Boolean(
selected_count && selected_count < nodes.length);
},
_get_column_filter: function(column) {
if (column instanceof Sao.View.Tree.ButtonColumn) {
return false;
@@ -26251,6 +26343,7 @@ function eval_pyson(value){
selected_nodes[selected_nodes.length - 1].data.__record : null;
this.screen.current_record = current;
this._update_copy_state();
this._sync_selection_header_checkbox();
},
_on_sort_changed: function() {
if (this._syncing_grid || !this.gridApi) {
@@ -26306,6 +26399,7 @@ function eval_pyson(value){
});
this._syncing_grid = false;
this._update_copy_state();
this._sync_selection_header_checkbox();
},
_sync_sort_state: function() {
var column_api = this.gridColumnApi || this.gridApi;

View File

@@ -11761,6 +11761,11 @@ html[theme="default"] .ag-grid-tree-grouping-panel input[type="checkbox"]:after
padding-left: 6px;
}
.ag-grid-tree-host .ag-header-cell[col-id="__selection__"] .ag-header-cell-comp-wrapper {
justify-content: center;
padding-left: 0;
}
.ag-grid-tree-host .ag-selection-checkbox,
.ag-grid-tree-host .ag-header-select-all {
display: flex;

View File

@@ -24613,7 +24613,10 @@ function eval_pyson(value){
onRowDoubleClicked: this._on_row_double_clicked.bind(this),
onSelectionChanged: this._on_selection_changed.bind(this),
onSortChanged: this._on_sort_changed.bind(this),
onFilterChanged: this._schedule_state_save.bind(this),
onFilterChanged: () => {
this._schedule_state_save();
this._sync_selection_header_checkbox();
},
onColumnMoved: this._on_grid_column_layout_changed.bind(this),
onColumnPinned: this._on_grid_column_layout_changed.bind(this),
onColumnResized: this._on_grid_column_resized.bind(this),
@@ -24715,6 +24718,7 @@ function eval_pyson(value){
sortable: false,
resizable: false,
filter: false,
headerComponent: this._get_selection_header_component(),
cellRenderer: this._create_selection_cell_renderer.bind(this),
});
}
@@ -24878,6 +24882,94 @@ function eval_pyson(value){
}
return checkbox;
},
_get_selection_header_component: function() {
var tree = this;
var SelectionHeader = function() {};
SelectionHeader.prototype.init = function() {
this.gui = tree._create_selection_header_renderer();
};
SelectionHeader.prototype.getGui = function() {
return this.gui;
};
SelectionHeader.prototype.destroy = function() {
if (tree._selection_header_checkbox === this.gui) {
tree._selection_header_checkbox = null;
}
this.gui = null;
};
return SelectionHeader;
},
_create_selection_header_renderer: function() {
if (this.selection_mode != Sao.common.SELECTION_MULTIPLE) {
return document.createTextNode('');
}
var checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'ag-custom-checkbox ag-custom-header-checkbox';
checkbox.title = Sao.i18n.gettext('Select all');
checkbox.setAttribute('aria-label', Sao.i18n.gettext('Select all'));
checkbox.addEventListener('click', event => {
event.stopPropagation();
});
checkbox.addEventListener('change', event => {
event.stopPropagation();
this._set_visible_records_selection(checkbox.checked);
});
this._selection_header_checkbox = checkbox;
window.requestAnimationFrame(() => {
this._sync_selection_header_checkbox();
});
return checkbox;
},
_get_selectable_grid_nodes: function() {
var nodes = [];
if (!this.gridApi) {
return nodes;
}
var collect = node => {
if (node && node.data && node.data.__kind == 'record' &&
node.data.__record) {
nodes.push(node);
}
};
if (this.gridApi.forEachNodeAfterFilter) {
this.gridApi.forEachNodeAfterFilter(collect);
} else {
this.gridApi.forEachNode(collect);
}
return nodes;
},
_set_visible_records_selection: function(value) {
if (this.selection_mode != Sao.common.SELECTION_MULTIPLE ||
!this.gridApi) {
return;
}
var nodes = this._get_selectable_grid_nodes();
this._syncing_grid = true;
try {
nodes.forEach(node => {
node.setSelected(Boolean(value));
});
} finally {
this._syncing_grid = false;
}
this._on_selection_changed();
this._sync_selection_header_checkbox();
},
_sync_selection_header_checkbox: function() {
var checkbox = this._selection_header_checkbox;
if (!checkbox ||
this.selection_mode != Sao.common.SELECTION_MULTIPLE) {
return;
}
var nodes = this._get_selectable_grid_nodes();
var selected_count = nodes.filter(node => node.isSelected()).length;
checkbox.disabled = !nodes.length;
checkbox.checked = Boolean(
nodes.length && selected_count == nodes.length);
checkbox.indeterminate = Boolean(
selected_count && selected_count < nodes.length);
},
_get_column_filter: function(column) {
if (column instanceof Sao.View.Tree.ButtonColumn) {
return false;
@@ -26251,6 +26343,7 @@ function eval_pyson(value){
selected_nodes[selected_nodes.length - 1].data.__record : null;
this.screen.current_record = current;
this._update_copy_state();
this._sync_selection_header_checkbox();
},
_on_sort_changed: function() {
if (this._syncing_grid || !this.gridApi) {
@@ -26306,6 +26399,7 @@ function eval_pyson(value){
});
this._syncing_grid = false;
this._update_copy_state();
this._sync_selection_header_checkbox();
},
_sync_sort_state: function() {
var column_api = this.gridColumnApi || this.gridApi;