Skip to main content

ravenrs_app_ui/
list_view.rs

1//! Many records: a [`DataGrid`] over the object space, shaped by the model.
2
3use std::collections::{BTreeMap, BTreeSet};
4use std::rc::Rc;
5
6use ravenrs_app::{
7    AppearanceTarget, CONTEXT_SAVE, Criteria, DataAccess, EntityInfo, FieldInfo, FieldKind, Frame, Message,
8    MessageKind, ModelNode, Object, Query, Severity, Value, ViewInfo, validate_in_place,
9};
10use ravenrs_grid_export::export;
11use ravenrs_reactive::{Binding, State};
12use ravenrs_widgets::grid::{GridQuery, Row, Value as Cell, to_table};
13use ravenrs_widgets::{
14    AnyView, BuildCx, CellEdit, DataGrid, GridCellStyle, GridColumn, GridData, GridPage, GridRequest,
15    GridResponder, GridRowAction, GridTable, Keyed, NodeId, RemoteRows, SelectionMode, SortDirection, Text,
16    View,
17};
18
19use crate::session::Session;
20use crate::values::{
21    cell_of, column_of, display, editable_in_list, key_from_text, key_text, semantic_of, value_of_cell,
22};
23
24/// What opening a row of a list does, with its key.
25pub type OpenHandler = Rc<dyn Fn(&Value)>;
26
27/// What a list editor is asked to show: the records as the list reads them, and how to act on them.
28#[non_exhaustive]
29#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
30pub struct ListEditorRequest {
31    /// The application it lives in.
32    pub session: Session,
33    /// The view it shows.
34    pub view: ViewInfo,
35    /// The entity of the records.
36    pub entity: EntityInfo,
37    /// The fields the list shows, with what their columns say.
38    pub columns: Vec<(FieldInfo, String)>,
39    /// The records, in the list's order.
40    pub objects: Rc<Vec<Object>>,
41    /// Opens a record, by its key.
42    pub on_open: OpenHandler,
43    /// Says which records are selected, which turns actions on and off.
44    pub on_select: Rc<dyn Fn(Vec<Object>)>,
45}
46
47impl std::fmt::Debug for ListEditorRequest {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        f.debug_struct("ListEditorRequest").field("view", &self.view).finish_non_exhaustive()
50    }
51}
52
53/// Shows a list some other way than in a grid.
54pub(crate) type ListEditorFactory = Rc<dyn Fn(&ListEditorRequest) -> AnyView>;
55
56/// What gives a list its records when no storage keeps them.
57pub type ListSource = Rc<dyn Fn() -> Vec<Object>>;
58
59/// What a column ends up as, from its field and the one the field gives.
60pub type ColumnShape = Rc<dyn Fn(&FieldInfo, GridColumn) -> GridColumn>;
61
62/// What a grid ends up as, from the one the list made.
63pub type GridShape = Rc<dyn Fn(DataGrid) -> DataGrid>;
64
65/// A list read from storage, kept until something may have changed it.
66#[derive(Clone)]
67struct Loaded {
68    revision: u64,
69    asked: String,
70    data: GridData,
71    objects: Rc<BTreeMap<String, Object>>,
72    ordered: Rc<Vec<Object>>,
73    /// On a list people edit: the cells that fail a rule, by the key of the record and the field.
74    invalid: Rc<BTreeSet<(String, String)>>,
75}
76
77/// A list that brings its rows as they are needed.
78#[derive(Clone)]
79struct Remote {
80    asked: String,
81    revision: u64,
82    rows: RemoteRows,
83}
84
85/// A field a list shows, what its column says, and whether the column shows.
86type Shown<'a> = (&'a FieldInfo, String, bool);
87
88/// The fields a list shows, in the order to show them, with the key last and out of sight, so
89/// a row can always be found.
90fn shown_fields<'a>(entity: &'a EntityInfo, view: &'a ModelNode, only: Option<&[String]>) -> Vec<Shown<'a>> {
91    let all = view.get("Columns");
92    let caption = |id: &str, field: &FieldInfo| {
93        all.and_then(|columns| columns.get(id))
94            .and_then(|column| column.text("Caption"))
95            .unwrap_or(&field.caption)
96            .to_owned()
97    };
98    let chosen: Vec<&str> = match only {
99        Some(names) => names.iter().map(String::as_str).collect(),
100        None => all
101            .map(|columns| columns.in_order())
102            .unwrap_or_default()
103            .into_iter()
104            .map(|column| column.id.as_str())
105            .collect(),
106    };
107    let mut fields: Vec<Shown<'a>> = chosen
108        .into_iter()
109        .filter_map(|id| entity.field_named(id).map(|field| (field, caption(id, field), true)))
110        .collect();
111    if let Some(key) = entity.key_field()
112        && !fields.iter().any(|(field, ..)| field.is_key)
113    {
114        fields.push((key, key.caption.clone(), false));
115    }
116    fields
117}
118
119/// The records of an entity in a grid: search, filters, sorting and export come with it. Opening
120/// a row opens its form.
121#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
122pub struct ListView {
123    session: Session,
124    frame: Frame,
125    view: ViewInfo,
126    on_open: Option<OpenHandler>,
127    compact: bool,
128    row_actions: Vec<GridRowAction>,
129    cell_text: Option<CellText>,
130    filter: Option<Criteria>,
131    sort: Vec<(String, bool)>,
132    column_shape: Option<ColumnShape>,
133    grid_shape: Option<GridShape>,
134    /// What goes without saying where this list is.
135    implied: Option<String>,
136    single: bool,
137    plain: bool,
138    remote: bool,
139    source: Option<ListSource>,
140}
141
142/// What a cell says instead of its value, for a field and a record; nothing means its value.
143pub type CellText = Rc<dyn Fn(&str, &Object) -> Option<String>>;
144
145impl std::fmt::Debug for ListView {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        f.debug_struct("ListView").field("view", &self.view).finish_non_exhaustive()
148    }
149}
150
151/// How a list reads its rows, without the view around it, so a remote loader can keep one.
152#[derive(Clone)]
153struct Reader {
154    session: Session,
155    frame: Frame,
156    cell_text: Option<CellText>,
157}
158
159#[cfg_attr(feature = "live", ravenrs_live::expose)]
160impl Reader {
161    /// What names each record of an entity, by its key as text.
162    fn names_of(&self, entity_name: &str) -> BTreeMap<String, String> {
163        let Some(entity) = self.session.entity(entity_name) else { return BTreeMap::new() };
164        let (Some(key), Some(name)) = (entity.key_field(), entity.default_property()) else {
165            return BTreeMap::new();
166        };
167        let found = self.frame.space().borrow().query(entity_name, &Query::all());
168        found
169            .unwrap_or_default()
170            .iter()
171            .map(|object| (display(key, &object.get(&key.name)), display(name, &object.get(&name.name))))
172            .collect()
173    }
174
175    /// A reference shows what names the record it points to, read once for the whole list.
176    fn names(&self, fields: &[FieldInfo]) -> BTreeMap<String, BTreeMap<String, String>> {
177        fields
178            .iter()
179            .filter_map(|field| match &field.kind {
180                FieldKind::Reference(target) => Some((field.name.clone(), self.names_of(target))),
181                _ => None,
182            })
183            .collect()
184    }
185
186    fn cell(
187        &self,
188        names: &BTreeMap<String, BTreeMap<String, String>>,
189        field: &FieldInfo,
190        object: &Object,
191    ) -> Cell {
192        let value = object.get(&field.name);
193        if let Some(known) = names.get(&field.name)
194            && !matches!(value, Value::Null)
195        {
196            let text = display(field, &value);
197            return Cell::Text(known.get(&text).cloned().unwrap_or(text));
198        }
199        match self.cell_text.as_ref().and_then(|text| text(&field.name, object)) {
200            Some(text) if text.is_empty() => Cell::Empty,
201            Some(text) => Cell::Text(text),
202            None => cell_of(field, &value),
203        }
204    }
205
206    fn rows(&self, fields: &[FieldInfo], objects: &[Object]) -> Vec<Row> {
207        let names = self.names(fields);
208        objects
209            .iter()
210            .map(|object| fields.iter().map(|field| self.cell(&names, field, object)).collect())
211            .collect()
212    }
213}
214
215/// `text` as a literal of the expression language.
216fn literal(text: &str) -> String {
217    format!("'{}'", text.replace('\'', "''"))
218}
219
220fn both(first: Option<Criteria>, second: Option<Criteria>) -> Option<Criteria> {
221    match (first, second) {
222        (Some(first), Some(second)) => Some(first.and(second)),
223        (first, second) => first.or(second),
224    }
225}
226
227/// The part of a remote request that says which rows, over the fields of its columns.
228fn request_filter(request: &GridRequest, fields: &[FieldInfo]) -> Option<Criteria> {
229    let searchable =
230        |field: &FieldInfo| matches!(field.kind, FieldKind::Text | FieldKind::LongText | FieldKind::Enum(_));
231    let contains = |field: &FieldInfo, text: &str| {
232        Criteria::parse(&format!("Contains(Upper([{}]), Upper({}))", field.name, literal(text))).ok()
233    };
234    let by_column = request.filters.iter().enumerate().fold(None, |all, (at, text)| {
235        let this = fields
236            .get(at)
237            .filter(|field| searchable(field) && !text.trim().is_empty())
238            .and_then(|field| contains(field, text.trim()));
239        both(all, this)
240    });
241    let typed = request.search.trim();
242    let searched = if typed.is_empty() {
243        None
244    } else {
245        fields
246            .iter()
247            .filter(|field| searchable(field))
248            .filter_map(|field| contains(field, typed))
249            .reduce(Criteria::or)
250    };
251    both(by_column, searched)
252}
253
254/// Rows brought from storage a chunk at a time. With no sorting asked for, a chunk continues from
255/// the key of the row before it, which does not get slower the further down it goes.
256fn answer_remote(
257    reader: &Reader,
258    entity: &EntityInfo,
259    fields: &[FieldInfo],
260    base: Option<&Criteria>,
261    sort: &[(String, bool)],
262    request: &GridRequest,
263    answer: GridResponder,
264) {
265    let filter = both(base.cloned(), request_filter(request, fields));
266    let key = entity.key_field();
267    let asked_sort: Vec<(String, bool)> = request
268        .sort
269        .iter()
270        .filter_map(|(column, direction)| {
271            let field = fields.get(*column)?;
272            Some((field.name.clone(), matches!(direction, SortDirection::Ascending)))
273        })
274        .collect();
275    let keyset = asked_sort.is_empty() && sort.is_empty();
276    let after = keyset
277        .then(|| {
278            let at = fields.iter().position(|field| field.is_key)?;
279            let (key, cell) = (key?, request.after.as_ref()?.get(at)?);
280            let after = crate::editors::literal_of(&key_from_text(entity, &cell.key_text()));
281            Criteria::parse(&format!("[{}] > {after}", key.name)).ok()
282        })
283        .flatten();
284    let offset = if after.is_some() { 0 } else { request.offset };
285    let mut query = Query::all().page(offset, request.limit + 1);
286    if let Some(filter) = both(filter, after) {
287        query = query.filter(filter);
288    }
289    let order = if asked_sort.is_empty() { sort.to_vec() } else { asked_sort };
290    for (field, ascending) in order {
291        query = query.sort(field, ascending);
292    }
293    if keyset && let Some(key) = key {
294        query = query.sort(key.name.clone(), true);
295    }
296    let found = reader.frame.space().borrow().query(&entity.name, &query);
297    match found {
298        Ok(mut objects) => {
299            let has_more = objects.len() > request.limit;
300            objects.truncate(request.limit);
301            answer.send(GridPage::new(reader.rows(fields, &objects), has_more));
302        }
303        Err(error) => answer.fail(error.to_string()),
304    }
305}
306
307#[cfg_attr(feature = "live", ravenrs_live::expose)]
308impl ListView {
309    /// The list of this view in the model, reading through this frame.
310    pub fn new(session: Session, frame: Frame, view: ViewInfo) -> Self {
311        Self {
312            session,
313            frame,
314            view,
315            on_open: None,
316            compact: false,
317            row_actions: Vec::new(),
318            cell_text: None,
319            filter: None,
320            sort: Vec::new(),
321            column_shape: None,
322            grid_shape: None,
323            implied: None,
324            single: false,
325            plain: false,
326            remote: false,
327            source: None,
328        }
329    }
330
331    /// Only the rows and their headers, as tall as they need, for a few records beside a form.
332    pub fn compact(mut self) -> Self {
333        self.compact = true;
334        self
335    }
336
337    /// A button at the end of every row.
338    pub fn row_action(mut self, action: GridRowAction) -> Self {
339        self.row_actions.push(action);
340        self
341    }
342
343    /// Says what a cell shows, when its value alone is not it.
344    pub fn cell_text(mut self, text: CellText) -> Self {
345        self.cell_text = Some(text);
346        self
347    }
348
349    /// What opening a row does, with its key. Its form by default.
350    pub fn on_open(mut self, handler: OpenHandler) -> Self {
351        self.on_open = Some(handler);
352        self
353    }
354
355    /// Shows only the records that meet this, besides what the way picked to see the list asks.
356    pub fn filter(mut self, filter: Option<Criteria>) -> Self {
357        self.filter = filter;
358        self
359    }
360
361    /// Orders the records by this field, after what was already asked for.
362    pub fn sort(mut self, field: impl Into<String>, ascending: bool) -> Self {
363        self.sort.push((field.into(), ascending));
364        self
365    }
366
367    /// Changes the column of a field: a template, a width, a summary, or one of its own.
368    pub fn column_shape(mut self, shape: impl Fn(&FieldInfo, GridColumn) -> GridColumn + 'static) -> Self {
369        self.column_shape = Some(Rc::new(shape));
370        self
371    }
372
373    /// Leaves out the column of this field, whose value is the same on every row.
374    pub fn implied(mut self, field: impl Into<String>) -> Self {
375        self.implied = Some(field.into());
376        self
377    }
378
379    /// Changes the grid before it goes on the screen, for what the list has no word for.
380    pub fn grid_shape(mut self, shape: impl Fn(DataGrid) -> DataGrid + 'static) -> Self {
381        self.grid_shape = Some(Rc::new(shape));
382        self
383    }
384
385    /// Only one row selected at a time.
386    pub fn single_selection(mut self) -> Self {
387        self.single = true;
388        self
389    }
390
391    /// Leaves out the filter builder and exporting to files, for a screen that has its own.
392    pub fn plain_toolbar(mut self) -> Self {
393        self.plain = true;
394        self
395    }
396
397    /// Brings rows from storage as they are needed, for lists too long to read at once.
398    /// Grouping needs the rows at hand, so it is off, and searching covers only fields with text.
399    pub fn remote(mut self) -> Self {
400        self.remote = true;
401        self
402    }
403
404    /// Reads its records from this and not from storage, for what no storage keeps.
405    pub fn source(mut self, source: ListSource) -> Self {
406        self.source = Some(source);
407        self
408    }
409
410    fn reader(&self) -> Reader {
411        Reader { session: self.session.clone(), frame: self.frame.clone(), cell_text: self.cell_text.clone() }
412    }
413
414    /// What this list asks of its records now.
415    fn active_filter(&self) -> Option<Criteria> {
416        both(self.filter.clone(), self.session.model_filter(&self.view.id))
417    }
418
419    fn load(
420        &self,
421        entity: &EntityInfo,
422        fields: &[FieldInfo],
423        filter: Option<&Criteria>,
424        revision: u64,
425        asked: String,
426    ) -> Loaded {
427        let space = self.frame.space();
428        let mut query = Query::all();
429        if let Some(filter) = filter {
430            query = query.filter(filter.clone());
431        }
432        for (field, ascending) in &self.sort {
433            query = query.sort(field.clone(), *ascending);
434        }
435        if self.access() == DataAccess::DataView {
436            query = query.only(fields.iter().map(|field| field.name.clone()));
437        }
438        let source = self.source.clone().or_else(|| self.session.record_source(&entity.name));
439        let found = match &source {
440            Some(source) => Ok(source()),
441            None => space.borrow().query(&entity.name, &query),
442        };
443        let objects = match found {
444            Ok(objects) => objects,
445            Err(error) => {
446                self.session.set_error(Some(error.to_string()));
447                Vec::new()
448            }
449        };
450        let ordered = Rc::new(objects.clone());
451        let key = entity.key_field().map(|f| f.name.clone()).unwrap_or_default();
452        let rows = self.reader().rows(fields, &objects);
453        let invalid = if self.edits_in_place(entity) {
454            let words = &self.session.words().rules;
455            let space = space.borrow();
456            objects
457                .iter()
458                .flat_map(|object| {
459                    let held = key_text(entity, &object.get(&key));
460                    validate_in_place(entity, object, CONTEXT_SAVE, &*space, words)
461                        .into_iter()
462                        .filter(|violation| violation.is_error())
463                        .filter_map(move |violation| Some((held.clone(), violation.field?)))
464                        .collect::<Vec<_>>()
465                })
466                .collect()
467        } else {
468            BTreeSet::new()
469        };
470        let by_key =
471            objects.into_iter().map(|object| (key_text(entity, &object.get(&key)), object)).collect();
472        Loaded {
473            revision,
474            asked,
475            data: GridData::new(rows),
476            objects: Rc::new(by_key),
477            ordered,
478            invalid: Rc::new(invalid),
479        }
480    }
481
482    /// How the model says this list gets its records.
483    fn access(&self) -> DataAccess {
484        let model = self.session.model();
485        if self.remote {
486            return DataAccess::Server;
487        }
488        model
489            .at(&["Views", &self.view.id])
490            .and_then(|node| node.text("DataAccess"))
491            .map_or(DataAccess::Client, DataAccess::from_name)
492    }
493
494    /// Whether people change values right on this list: it has to hold all its records, whole.
495    fn edits_in_place(&self, entity: &EntityInfo) -> bool {
496        let model = self.session.model();
497        let asked = model.at(&["Views", &self.view.id]).and_then(|node| node.flag("AllowEdit"));
498        asked == Some(true)
499            && self.access() == DataAccess::Client
500            && self.source.is_none()
501            && !entity.transient
502    }
503
504    /// Keeps only what still exists selected, as it is now, so actions judge it as it is.
505    fn follow_selection(&self, chosen: &State<BTreeSet<String>>, objects: &BTreeMap<String, Object>) {
506        let left: BTreeSet<String> =
507            chosen.peek().iter().filter(|key| objects.contains_key(*key)).cloned().collect();
508        if left.len() != chosen.peek().len() {
509            chosen.set(left.clone());
510        }
511        let whole = self.access() != DataAccess::DataView;
512        let fresh: Vec<Object> = left
513            .iter()
514            .filter_map(|key| {
515                if whole {
516                    return objects.get(key).cloned();
517                }
518                // A list that reads only its columns does not have what actions judge by.
519                let (name, value) = objects.get(key).map(|object| (object.entity().to_owned(), key))?;
520                let entity = self.session.entity(&name)?;
521                let value = key_from_text(&entity, value);
522                self.frame.space().borrow().find(&name, &value).ok().flatten()
523            })
524            .collect();
525        if self.frame.selected() != fresh {
526            self.frame.set_selected(fresh);
527        }
528    }
529
530    fn remote_rows(
531        &self,
532        cx: &mut BuildCx<'_>,
533        entity: &EntityInfo,
534        fields: &[FieldInfo],
535        filter: Option<&Criteria>,
536        revision: u64,
537        asked: &str,
538    ) -> RemoteRows {
539        let held: State<Option<Remote>> = cx.use_state(|| None);
540        let kept = held.peek().clone().filter(|kept| kept.asked == asked);
541        match kept {
542            Some(kept) => {
543                if kept.revision != revision {
544                    kept.rows.refresh();
545                    held.set(Some(Remote { revision, ..kept.clone() }));
546                }
547                kept.rows
548            }
549            None => {
550                let (reader, entity, fields) = (self.reader(), entity.clone(), fields.to_vec());
551                let (base, sort) = (filter.cloned(), self.sort.clone());
552                let rows = RemoteRows::new(move |request, answer| {
553                    answer_remote(&reader, &entity, &fields, base.as_ref(), &sort, &request, answer);
554                });
555                held.set(Some(Remote { asked: asked.to_owned(), revision, rows: rows.clone() }));
556                rows
557            }
558        }
559    }
560}
561
562impl View for ListView {
563    fn build(&self, cx: &mut BuildCx<'_>) -> NodeId {
564        let session = &self.session;
565        let model = session.model();
566        let (Some(name), Some(node)) = (self.view.entity.as_deref(), model.at(&["Views", &self.view.id]))
567        else {
568            return cx.child(&Text::new(self.view.id.clone()));
569        };
570        let Some(entity) = session.entity(name) else {
571            return cx.child(&Text::new(name.to_owned()));
572        };
573        let only = session.variant_columns(&self.view.id);
574        let mut fields = shown_fields(&entity, node, only.as_deref());
575        for (field, _, visible) in &mut fields {
576            if self.implied.as_deref() == Some(field.name.as_str()) {
577                *visible = false;
578            }
579        }
580        let infos: Vec<FieldInfo> = fields.iter().map(|(field, ..)| (*field).clone()).collect();
581        let revision = session.revision();
582        let filter = self.active_filter();
583        let asked =
584            format!("{}|{:?}", filter.as_ref().map(ToString::to_string).unwrap_or_default(), self.sort);
585        let chosen: State<BTreeSet<String>> = cx.use_state(BTreeSet::new);
586        let editor_name = session
587            .variant_editor(&self.view.id)
588            .or_else(|| node.text("EditorType").map(str::to_owned))
589            .or_else(|| {
590                model.get("Views").and_then(|views| views.text("DefaultListEditor")).map(str::to_owned)
591            });
592        let editor = editor_name.as_deref().and_then(|name| session.list_editor(name));
593        if let Some(editor) = editor {
594            let loaded = self.load(&entity, &infos, filter.as_ref(), revision, asked);
595            let (opener, open_entity, on_open) = (session.clone(), entity.clone(), self.on_open.clone());
596            let frame = self.frame.clone();
597            let request = ListEditorRequest {
598                session: session.clone(),
599                view: self.view.clone(),
600                entity: entity.clone(),
601                columns: fields
602                    .iter()
603                    .filter(|(_, _, visible)| *visible)
604                    .map(|(field, caption, _)| ((*field).clone(), caption.clone()))
605                    .collect(),
606                objects: loaded.ordered,
607                on_open: Rc::new(move |key: &Value| match &on_open {
608                    Some(on_open) => on_open(key),
609                    None => opener.open_detail(&open_entity.name, key),
610                }),
611                on_select: Rc::new(move |records| frame.set_selected(records)),
612            };
613            // Keyed, as what one way of showing keeps is not for another.
614            let shown = editor(&request);
615            let key = editor_name.as_deref().map_or(0, |name| {
616                name.bytes()
617                    .fold(0xcbf2_9ce4_8422_2325_u64, |h, b| (h ^ u64::from(b)).wrapping_mul(0x100_0000_01b3))
618            });
619            return cx.keyed_child(key, shown.as_ref());
620        }
621        let access = self.access();
622        let (data, objects, remote_rows, ordered, invalid) = if access == DataAccess::Server {
623            let rows = self.remote_rows(cx, &entity, &infos, filter.as_ref(), revision, &asked);
624            let none = (Rc::new(Vec::new()), Rc::new(BTreeSet::new()));
625            (GridData::new(Vec::new()), Rc::new(BTreeMap::new()), Some(rows), none.0, none.1)
626        } else {
627            let loaded: State<Option<Loaded>> = cx.use_state(|| None);
628            let kept = loaded.peek().clone().filter(|kept| kept.revision == revision && kept.asked == asked);
629            let current = match kept {
630                Some(kept) => kept,
631                None => {
632                    let fresh = self.load(&entity, &infos, filter.as_ref(), revision, asked);
633                    self.follow_selection(&chosen, &fresh.objects);
634                    loaded.set(Some(fresh.clone()));
635                    fresh
636                }
637            };
638            (current.data, current.objects, None, current.ordered, current.invalid)
639        };
640        let in_place = self.edits_in_place(&entity);
641        let key_at = fields.iter().position(|(field, ..)| field.is_key).unwrap_or(0);
642        let words = session.words();
643        let default_property = entity.default_property().map(|f| f.name.clone());
644        let names_the_record = |field: &FieldInfo| default_property.as_deref() == Some(field.name.as_str());
645        let columns: Vec<GridColumn> = fields
646            .iter()
647            .map(|(field, caption, visible)| {
648                let column = column_of(field, caption, words);
649                // What names a record takes the room left over, so rows reach across.
650                let column = if names_the_record(field) { column.fill() } else { column };
651                // A column is for all its rows, so only a rule with no condition can hide it.
652                let hidden =
653                    entity.appearance_of(AppearanceTarget::Item, &field.name, &self.view, None).visible
654                        == Some(false);
655                let column = if *visible && !hidden { column } else { column.hidden() };
656                let column = if in_place && editable_in_list(field) { column.editable() } else { column };
657                match &self.column_shape {
658                    Some(shape) => shape(field, column),
659                    None => column,
660                }
661            })
662            .collect();
663        let selected = {
664            let (read, write) = (chosen.clone(), chosen.clone());
665            let (frame, known, found_in) = (self.frame.clone(), objects.clone(), entity.clone());
666            // What holds only part of the records finds them when they are selected.
667            let remote = remote_rows.is_some() || access == DataAccess::DataView;
668            Binding::new(
669                move || read.get(),
670                move |keys: BTreeSet<String>| {
671                    let records: Vec<Object> = keys
672                        .iter()
673                        .filter_map(|key| {
674                            if !remote {
675                                return known.get(key).cloned();
676                            }
677                            let value = key_from_text(&found_in, key);
678                            frame.space().borrow().find(&found_in.name, &value).ok().flatten()
679                        })
680                        .collect();
681                    frame.set_selected(records);
682                    write.set(keys);
683                },
684            )
685        };
686        let (opener, open_entity, delete) = (session.clone(), entity.clone(), session.clone());
687        let on_open = self.on_open.clone();
688        let saver = cx.file_picker();
689        let (exporter, caption) =
690            (session.clone(), node.text("Caption").unwrap_or(&entity.caption).to_owned());
691        let looks = (!entity.appearance.is_empty() || !invalid.is_empty()).then(|| {
692            let names: Vec<String> = fields.iter().map(|(field, ..)| field.name.clone()).collect();
693            let (rules, view, known) = (entity.clone(), self.view.clone(), objects.clone());
694            let invalid = invalid.clone();
695            move |row: &Row, column: usize| {
696                let held = row.get(key_at).map(|cell| cell.key_text()).unwrap_or_default();
697                let object = known.get(&held);
698                let Some(name) = names.get(column) else { return GridCellStyle::default() };
699                if invalid.contains(&(held, name.clone())) {
700                    return GridCellStyle::new(Some(ravenrs_style::Semantic::Danger), None, true);
701                }
702                let found = rules.appearance_of(AppearanceTarget::Item, name, &view, object);
703                GridCellStyle::new(found.font.map(semantic_of), found.back.map(semantic_of), found.bold)
704            }
705        });
706        let grid = match remote_rows {
707            Some(rows) => DataGrid::remote(rows, columns),
708            None => DataGrid::new(data, columns),
709        };
710        let grid = grid
711            .striped(true)
712            .key_column(key_at)
713            .selection_mode(if self.single { SelectionMode::Single } else { SelectionMode::Multiple })
714            .selected_keys(selected)
715            .export_title(caption)
716            .on_row_open(move |row| {
717                if let Some(cell) = row.get(key_at) {
718                    let key = key_from_text(&open_entity, &cell.key_text());
719                    match &on_open {
720                        Some(on_open) => on_open(&key),
721                        None => opener.open_detail(&open_entity.name, &key),
722                    }
723                }
724            })
725            .on_delete_rows(move |_| {
726                if let Some(action) = delete.frame().action(crate::ids::DELETE) {
727                    delete.run_action(&action, ravenrs_app::ActionInput::None);
728                }
729            });
730        let grid = if self.plain {
731            grid
732        } else {
733            grid.filter_builder(true).on_export_file(move |format, table| {
734                match export(&table, format, None) {
735                    Ok(file) => exporter.hand_file(file.name, file.bytes, &saver),
736                    Err(error) => exporter.set_error(Some(error)),
737                }
738            })
739        };
740        let grid = match looks {
741            Some(looks) => grid.cell_style(looks),
742            None => grid,
743        };
744        let grid = match default_property
745            .as_deref()
746            .and_then(|name| fields.iter().position(|(f, ..)| f.name == name))
747        {
748            Some(column) => grid.name_column(column),
749            None => grid,
750        };
751        let grid = self.row_actions.iter().cloned().fold(grid, DataGrid::row_action);
752        let grid = if in_place {
753            let (session, frame, named, shown) =
754                (session.clone(), self.frame.clone(), entity.clone(), infos.clone());
755            grid.on_cell_edit(move |edit| edit_on_list(&session, &frame, &named, &shown, &ordered, edit))
756        } else {
757            grid
758        };
759        let grid = if self.compact {
760            let height = objects.len().max(1) as f32 * 36.0 + 42.0;
761            grid.toolbar(false)
762                .filter_row(false)
763                .footer(false)
764                .selection_mode(SelectionMode::None)
765                .height(height)
766        } else {
767            grid
768        };
769        let grid = match &self.grid_shape {
770            Some(shape) => shape(grid),
771            None => grid,
772        };
773        // Another set of columns starts over, since the grid keeps what it knows by position.
774        match only {
775            Some(names) => cx.child(&Keyed::new(hash_of(&names.join(",")), grid)),
776            None => cx.child(&grid),
777        }
778    }
779}
780
781/// Keeps a value changed right on a list, in the record as storage has it now, if its rules let it
782/// stand: a field already wrong elsewhere on the record does not stop it, and what only warns goes
783/// on with a message.
784fn edit_on_list(
785    session: &Session,
786    frame: &Frame,
787    entity: &EntityInfo,
788    fields: &[FieldInfo],
789    ordered: &[Object],
790    edit: &CellEdit,
791) -> Result<(), String> {
792    let words = session.words();
793    let field = fields.get(edit.column).ok_or_else(|| words.rules.record.clone())?;
794    let invalid = || words.rules.invalid.replace("{0}", &field.caption);
795    let value = value_of_cell(field, &edit.value).ok_or_else(invalid)?;
796    let key_name = entity.key_field().map(|key| key.name.clone()).unwrap_or_default();
797    let key = ordered.get(edit.row).map(|object| object.get(&key_name)).ok_or_else(invalid)?;
798    let space = frame.space();
799    let stored = space.borrow().find(&entity.name, &key).map_err(|e| e.to_string())?;
800    let mut record = stored.ok_or_else(|| words.record_gone.clone())?;
801    record.set(&field.name, value);
802    let found = session.violations_of(&record, CONTEXT_SAVE);
803    let stops = |violation: &&ravenrs_app::Violation| {
804        violation.is_error() && violation.field.as_ref().is_none_or(|name| *name == field.name)
805    };
806    if let Some(violation) = found.iter().find(stops) {
807        return Err(violation.message.clone());
808    }
809    let kept = {
810        let mut space = space.borrow_mut();
811        space.save(record).and_then(|()| space.commit())
812    };
813    if let Err(error) = kept {
814        space.borrow_mut().rollback();
815        return Err(error.to_string());
816    }
817    for violation in found.iter().filter(|violation| violation.severity == Severity::Warning) {
818        session.show_message(Message::new(violation.message.clone(), MessageKind::Warning));
819    }
820    session.touch();
821    Ok(())
822}
823
824fn hash_of(text: &str) -> u64 {
825    text.bytes().fold(0xcbf2_9ce4_8422_2325_u64, |h, b| (h ^ u64::from(b)).wrapping_mul(0x100_0000_01b3))
826}
827
828#[cfg_attr(feature = "live", ravenrs_live::expose)]
829impl Session {
830    /// The records of a list, the way it chose to see them, as this person can read them.
831    pub fn list_records(&self, view: &ViewInfo) -> Vec<Object> {
832        let Some(name) = view.entity.as_deref() else { return Vec::new() };
833        let mut query = Query::all();
834        if let Some(filter) = self.model_filter(&view.id) {
835            query = query.filter(filter);
836        }
837        self.frame().space().borrow().query(name, &query).unwrap_or_default()
838    }
839
840    /// A list as it stands: the columns showing and every record the way it chose to see them, as
841    /// a table to make a report or a file from.
842    pub fn list_table(&self, view: &ViewInfo) -> Option<GridTable> {
843        let model = self.model();
844        let (name, node) = (view.entity.as_deref()?, model.at(&["Views", &view.id])?);
845        let entity = self.entity(name)?;
846        let mut query = Query::all();
847        if let Some(filter) = self.model_filter(&view.id) {
848            query = query.filter(filter);
849        }
850        let objects = self.frame().space().borrow().query(name, &query).ok()?;
851        let only = self.variant_columns(&view.id);
852        let shown: Vec<(FieldInfo, String)> = shown_fields(&entity, node, only.as_deref())
853            .into_iter()
854            .filter(|(_, _, visible)| *visible)
855            .map(|(field, caption, _)| (field.clone(), caption))
856            .collect();
857        let infos: Vec<FieldInfo> = shown.iter().map(|(field, _)| field.clone()).collect();
858        let reader = Reader { session: self.clone(), frame: self.frame(), cell_text: None };
859        let rows = reader.rows(&infos, &objects);
860        let words = self.words();
861        let columns: Vec<GridColumn> =
862            shown.iter().map(|(field, caption)| column_of(field, caption, words)).collect();
863        let culture = if words.decimal == ',' { "pt-BR" } else { "en-US" };
864        let title = node.text("Caption").unwrap_or(&entity.caption);
865        let order: Vec<usize> = (0..rows.len()).collect();
866        Some(to_table(title, culture, &columns, &rows, &GridQuery::default(), &order))
867    }
868}