diff --git a/assets/schema/ensemble_schema.json b/assets/schema/ensemble_schema.json index 87698edd1..fe9abb5a0 100644 --- a/assets/schema/ensemble_schema.json +++ b/assets/schema/ensemble_schema.json @@ -6103,6 +6103,45 @@ "$ref": "#/$defs/Widget", "description": "If spans per row overflow based on spanPerRow than overflowWidget will render" }, + "span-template": { + "type": "object", + "required": [ + "data", + "name", + "span" + ], + "properties": { + "data": { + "type": "string", + "description": "Bind the rowspan list to the data e.g. myAPI.body.items" + }, + "name": { + "type": "string", + "description": "Give this a name. This is the bindable data for each rowspan." + }, + "span": { + "type": "object", + "required": [ + "start", + "end", + "widget" + ], + "properties": { + "start": { + "type": "string", + "description": "Starting date of the rowspan in ISO format" + }, + "end": { + "type": "string", + "description": "Ending date of the rowspan in ISO format" + }, + "widget": { + "$ref": "#/$defs/Widget" + } + } + } + } + }, "children": { "type": "array", "items": { diff --git a/lib/framework/action.dart b/lib/framework/action.dart index 33d586359..91b23f705 100644 --- a/lib/framework/action.dart +++ b/lib/framework/action.dart @@ -76,7 +76,8 @@ class ShowDialogAction extends EnsembleAction { options: Utils.getMap(payload['options']), onDialogDismiss: payload['onDialogDismiss'] == null ? null - : EnsembleAction.fromYaml(Utils.maybeYamlMap(payload['onDialogDismiss'])), + : EnsembleAction.fromYaml( + Utils.maybeYamlMap(payload['onDialogDismiss'])), ); } } diff --git a/lib/util/utils.dart b/lib/util/utils.dart index e6c56dfe8..4ee90b457 100644 --- a/lib/util/utils.dart +++ b/lib/util/utils.dart @@ -316,13 +316,15 @@ class Utils { Map? map = getMap(value); return map != null ? YamlMap.wrap(map) : null; } + //this is semantically different from the methods above as it is doesn't return null when value is not a map static dynamic maybeYamlMap(dynamic value) { - if ( value is Map ) { + if (value is Map) { return YamlMap.wrap(value); } return value; } + static Color? getColor(dynamic value) { if (value is String) { switch (value) { diff --git a/lib/widget/calendar.dart b/lib/widget/calendar.dart index 6bcdd2859..b24d2909e 100644 --- a/lib/widget/calendar.dart +++ b/lib/widget/calendar.dart @@ -5,10 +5,13 @@ import 'package:ensemble/framework/model.dart'; import 'package:ensemble/framework/scope.dart'; import 'package:ensemble/framework/view/data_scope_widget.dart'; import 'package:ensemble/framework/widget/widget.dart'; +import 'package:ensemble/layout/templated.dart'; +import 'package:ensemble/page_model.dart'; import 'package:ensemble/screen_controller.dart'; import 'package:ensemble/util/extensions.dart'; import 'package:ensemble/util/utils.dart'; import 'package:ensemble/widget/helpers/controllers.dart'; +import 'package:ensemble_ts_interpreter/extensions.dart'; import 'package:ensemble_ts_interpreter/invokables/invokable.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -91,6 +94,12 @@ class EnsembleCalendar extends StatefulWidget Utils.getInt(value['spanPerRow'], fallback: -1); _controller.overlapOverflowBuilder = value['overflowWidget']; _controller.topMargin = Utils.getInt(value['topMargin'], fallback: 0); + + if (value['span-template'] is YamlMap || + value['span-template'] is Map) { + setRowSpanItemTemplate(value['span-template']); + } + if (value['children'] is List) { for (var span in value['children']) { setRowSpan(span['span']); @@ -106,6 +115,30 @@ class EnsembleCalendar extends StatefulWidget }; } + void setRowSpanItemTemplate(dynamic rowSpanData) { + try { + if (rowSpanData is! Map || + rowSpanData is! YamlMap || + rowSpanData['span'] is! Map || + rowSpanData['span'] is! YamlMap) { + return; + } + + final rowSpanTemplate = RowSpanTemplate( + data: rowSpanData['data'], + name: rowSpanData['name'], + spanTemplate: SpanTemplate( + start: rowSpanData['span']['start'], + end: rowSpanData['span']['end'], + widget: rowSpanData['span']['widget'], + ), + ); + _controller.rowSpanTemplate = rowSpanTemplate; + } on Exception catch (_) { + //noop + } + } + setTooltip(value) { _controller.tooltip = Utils.optionalString(value?['text']); _controller.tooltipBackgroundColor = @@ -386,6 +419,7 @@ class EnsembleCalendar extends StatefulWidget } class RowSpanConfig { + ScopeManager? scope; DateTime? startDay; DateTime? endDay; dynamic widget; @@ -398,6 +432,7 @@ class RowSpanConfig { this.widget, this.inputs, required this.id, + this.scope, }); bool get isValid => startDay != null && endDay != null; @@ -457,6 +492,7 @@ class CalendarController extends WidgetController { int topMargin = 0; dynamic overlapOverflowBuilder; + RowSpanTemplate? rowSpanTemplate; DateTime? selectedDate; DateTime? disabledDate; @@ -506,8 +542,10 @@ int getHashCode(DateTime key) { return key.day * 1000000 + key.month * 10000 + key.year; } -class CalendarState extends WidgetState { +class CalendarState extends WidgetState + with TemplatedWidgetState { final CalendarFormat _calendarFormat = CalendarFormat.month; + ItemTemplate? itemTemplate; @override void initState() { @@ -522,6 +560,51 @@ class CalendarState extends WidgetState { setState(() {}); } + @override + void didChangeDependencies() { + _registerRowSpanListener(context); + super.didChangeDependencies(); + } + + void _registerRowSpanListener(BuildContext context) { + if (widget.controller.rowSpanTemplate != null) { + registerItemTemplate(context, widget.controller.rowSpanTemplate!, + evaluateInitialValue: true, onDataChanged: (dataList) { + if (dataList is List) { + final configs = _builRowSpanConfigs(context, dataList); + widget._controller.rowSpans.value = configs; + setState(() {}); + } + }); + } + } + + _builRowSpanConfigs(BuildContext context, List dataList) { + List rowSpanConfigs = []; + + RowSpanTemplate? itemTemplate = widget.controller.rowSpanTemplate; + ScopeManager? myScope = DataScopeWidget.getScope(context); + if (myScope != null && itemTemplate != null) { + for (dynamic dataItem in dataList) { + ScopeManager dataScope = myScope.createChildScope(); + dataScope.dataContext.addDataContextById(itemTemplate.name, dataItem); + + rowSpanConfigs.add( + RowSpanConfig( + id: Utils.generateRandomId(6), + startDay: Utils.getDate( + dataScope.dataContext.eval(itemTemplate.spanTemplate.start)), + endDay: Utils.getDate( + dataScope.dataContext.eval(itemTemplate.spanTemplate.end)), + scope: dataScope, + widget: itemTemplate.spanTemplate.widget, + ), + ); + } + } + return rowSpanConfigs; + } + @override void dispose() { widget._controller.selectedDays.removeListener(listener); @@ -683,6 +766,11 @@ class CalendarState extends WidgetState { final span = spans.value .firstWhereOrNull((element) => element.id == range.id); if (span != null) { + if (span.scope != null) { + final child = + span.scope?.buildWidgetFromDefinition(span.widget); + return child; + } return widgetBuilder( context, span.widget, @@ -918,3 +1006,22 @@ class _CalendarHeader extends StatelessWidget { ); } } + +class RowSpanTemplate extends ItemTemplate { + RowSpanTemplate({ + required String data, + required String name, + dynamic template, + required this.spanTemplate, + }) : super(data, name, template); + + final SpanTemplate spanTemplate; +} + +class SpanTemplate { + final String start; + final String end; + final dynamic widget; + + SpanTemplate({required this.start, required this.end, this.widget}); +}