Notes
This commit is contained in:
@@ -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`
|
||||
|
||||
@@ -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
5
dist/tryton-sao.css
vendored
@@ -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
96
dist/tryton-sao.js
vendored
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user