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 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
|
- 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
|
### Commits importants
|
||||||
|
|
||||||
- `fc9ab5d Save SAO AG Grid work`
|
- `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: 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
|
- 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
|
- 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
|
### 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
|
- 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: 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 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`
|
- 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
|
- 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
|
- 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;
|
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-selection-checkbox,
|
||||||
.ag-grid-tree-host .ag-header-select-all {
|
.ag-grid-tree-host .ag-header-select-all {
|
||||||
display: flex;
|
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),
|
onRowDoubleClicked: this._on_row_double_clicked.bind(this),
|
||||||
onSelectionChanged: this._on_selection_changed.bind(this),
|
onSelectionChanged: this._on_selection_changed.bind(this),
|
||||||
onSortChanged: this._on_sort_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),
|
onColumnMoved: this._on_grid_column_layout_changed.bind(this),
|
||||||
onColumnPinned: 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),
|
onColumnResized: this._on_grid_column_resized.bind(this),
|
||||||
@@ -24715,6 +24718,7 @@ function eval_pyson(value){
|
|||||||
sortable: false,
|
sortable: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
filter: false,
|
filter: false,
|
||||||
|
headerComponent: this._get_selection_header_component(),
|
||||||
cellRenderer: this._create_selection_cell_renderer.bind(this),
|
cellRenderer: this._create_selection_cell_renderer.bind(this),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -24878,6 +24882,94 @@ function eval_pyson(value){
|
|||||||
}
|
}
|
||||||
return checkbox;
|
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) {
|
_get_column_filter: function(column) {
|
||||||
if (column instanceof Sao.View.Tree.ButtonColumn) {
|
if (column instanceof Sao.View.Tree.ButtonColumn) {
|
||||||
return false;
|
return false;
|
||||||
@@ -26251,6 +26343,7 @@ function eval_pyson(value){
|
|||||||
selected_nodes[selected_nodes.length - 1].data.__record : null;
|
selected_nodes[selected_nodes.length - 1].data.__record : null;
|
||||||
this.screen.current_record = current;
|
this.screen.current_record = current;
|
||||||
this._update_copy_state();
|
this._update_copy_state();
|
||||||
|
this._sync_selection_header_checkbox();
|
||||||
},
|
},
|
||||||
_on_sort_changed: function() {
|
_on_sort_changed: function() {
|
||||||
if (this._syncing_grid || !this.gridApi) {
|
if (this._syncing_grid || !this.gridApi) {
|
||||||
@@ -26306,6 +26399,7 @@ function eval_pyson(value){
|
|||||||
});
|
});
|
||||||
this._syncing_grid = false;
|
this._syncing_grid = false;
|
||||||
this._update_copy_state();
|
this._update_copy_state();
|
||||||
|
this._sync_selection_header_checkbox();
|
||||||
},
|
},
|
||||||
_sync_sort_state: function() {
|
_sync_sort_state: function() {
|
||||||
var column_api = this.gridColumnApi || this.gridApi;
|
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;
|
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-selection-checkbox,
|
||||||
.ag-grid-tree-host .ag-header-select-all {
|
.ag-grid-tree-host .ag-header-select-all {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -24613,7 +24613,10 @@ function eval_pyson(value){
|
|||||||
onRowDoubleClicked: this._on_row_double_clicked.bind(this),
|
onRowDoubleClicked: this._on_row_double_clicked.bind(this),
|
||||||
onSelectionChanged: this._on_selection_changed.bind(this),
|
onSelectionChanged: this._on_selection_changed.bind(this),
|
||||||
onSortChanged: this._on_sort_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),
|
onColumnMoved: this._on_grid_column_layout_changed.bind(this),
|
||||||
onColumnPinned: 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),
|
onColumnResized: this._on_grid_column_resized.bind(this),
|
||||||
@@ -24715,6 +24718,7 @@ function eval_pyson(value){
|
|||||||
sortable: false,
|
sortable: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
filter: false,
|
filter: false,
|
||||||
|
headerComponent: this._get_selection_header_component(),
|
||||||
cellRenderer: this._create_selection_cell_renderer.bind(this),
|
cellRenderer: this._create_selection_cell_renderer.bind(this),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -24878,6 +24882,94 @@ function eval_pyson(value){
|
|||||||
}
|
}
|
||||||
return checkbox;
|
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) {
|
_get_column_filter: function(column) {
|
||||||
if (column instanceof Sao.View.Tree.ButtonColumn) {
|
if (column instanceof Sao.View.Tree.ButtonColumn) {
|
||||||
return false;
|
return false;
|
||||||
@@ -26251,6 +26343,7 @@ function eval_pyson(value){
|
|||||||
selected_nodes[selected_nodes.length - 1].data.__record : null;
|
selected_nodes[selected_nodes.length - 1].data.__record : null;
|
||||||
this.screen.current_record = current;
|
this.screen.current_record = current;
|
||||||
this._update_copy_state();
|
this._update_copy_state();
|
||||||
|
this._sync_selection_header_checkbox();
|
||||||
},
|
},
|
||||||
_on_sort_changed: function() {
|
_on_sort_changed: function() {
|
||||||
if (this._syncing_grid || !this.gridApi) {
|
if (this._syncing_grid || !this.gridApi) {
|
||||||
@@ -26306,6 +26399,7 @@ function eval_pyson(value){
|
|||||||
});
|
});
|
||||||
this._syncing_grid = false;
|
this._syncing_grid = false;
|
||||||
this._update_copy_state();
|
this._update_copy_state();
|
||||||
|
this._sync_selection_header_checkbox();
|
||||||
},
|
},
|
||||||
_sync_sort_state: function() {
|
_sync_sort_state: function() {
|
||||||
var column_api = this.gridColumnApi || this.gridApi;
|
var column_api = this.gridColumnApi || this.gridApi;
|
||||||
|
|||||||
Reference in New Issue
Block a user