1use 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
24pub type OpenHandler = Rc<dyn Fn(&Value)>;
26
27#[non_exhaustive]
29#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
30pub struct ListEditorRequest {
31 pub session: Session,
33 pub view: ViewInfo,
35 pub entity: EntityInfo,
37 pub columns: Vec<(FieldInfo, String)>,
39 pub objects: Rc<Vec<Object>>,
41 pub on_open: OpenHandler,
43 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
53pub(crate) type ListEditorFactory = Rc<dyn Fn(&ListEditorRequest) -> AnyView>;
55
56pub type ListSource = Rc<dyn Fn() -> Vec<Object>>;
58
59pub type ColumnShape = Rc<dyn Fn(&FieldInfo, GridColumn) -> GridColumn>;
61
62pub type GridShape = Rc<dyn Fn(DataGrid) -> DataGrid>;
64
65#[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 invalid: Rc<BTreeSet<(String, String)>>,
75}
76
77#[derive(Clone)]
79struct Remote {
80 asked: String,
81 revision: u64,
82 rows: RemoteRows,
83}
84
85type Shown<'a> = (&'a FieldInfo, String, bool);
87
88fn 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#[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 implied: Option<String>,
136 single: bool,
137 plain: bool,
138 remote: bool,
139 source: Option<ListSource>,
140}
141
142pub 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#[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 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 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
215fn 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
227fn 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
254fn 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 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 pub fn compact(mut self) -> Self {
333 self.compact = true;
334 self
335 }
336
337 pub fn row_action(mut self, action: GridRowAction) -> Self {
339 self.row_actions.push(action);
340 self
341 }
342
343 pub fn cell_text(mut self, text: CellText) -> Self {
345 self.cell_text = Some(text);
346 self
347 }
348
349 pub fn on_open(mut self, handler: OpenHandler) -> Self {
351 self.on_open = Some(handler);
352 self
353 }
354
355 pub fn filter(mut self, filter: Option<Criteria>) -> Self {
357 self.filter = filter;
358 self
359 }
360
361 pub fn sort(mut self, field: impl Into<String>, ascending: bool) -> Self {
363 self.sort.push((field.into(), ascending));
364 self
365 }
366
367 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 pub fn implied(mut self, field: impl Into<String>) -> Self {
375 self.implied = Some(field.into());
376 self
377 }
378
379 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 pub fn single_selection(mut self) -> Self {
387 self.single = true;
388 self
389 }
390
391 pub fn plain_toolbar(mut self) -> Self {
393 self.plain = true;
394 self
395 }
396
397 pub fn remote(mut self) -> Self {
400 self.remote = true;
401 self
402 }
403
404 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 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 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 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 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 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 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 let column = if names_the_record(field) { column.fill() } else { column };
651 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 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 match only {
775 Some(names) => cx.child(&Keyed::new(hash_of(&names.join(",")), grid)),
776 None => cx.child(&grid),
777 }
778 }
779}
780
781fn 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 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 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}