Skip to content

Commit

Permalink
Merge pull request magritte-metamodel#366 from seandenigris/enh_gt-dy…
Browse files Browse the repository at this point in the history
…namic-validation

[Enh]: Dynamic Form Validation in GT Bloc
  • Loading branch information
seandenigris authored Jan 19, 2025
2 parents 6d46b62 + f2c6410 commit 508ce34
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 55 deletions.
142 changes: 142 additions & 0 deletions source/Magritte-GToolkit/MABrTextEditor.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
Class {
#name : #MABrTextEditor,
#superclass : #BrEditor,
#instVars : [
'elementDescription',
'builder',
'errors',
'header'
],
#category : #'Magritte-GToolkit'
}

{ #category : #accessing }
MABrTextEditor class >> elementDescription: anMAElementDescription [

^ self basicNew
elementDescription: anMAElementDescription;
initialize
]

{ #category : #accessing }
MABrTextEditor class >> onDescription: anMAElementDescription forBuilder: anMAElementBuilder [

^ self basicNew
elementDescription: anMAElementDescription;
builder: anMAElementBuilder;
initialize
]

{ #category : #accessing }
MABrTextEditor >> builder [
^ builder
]

{ #category : #accessing }
MABrTextEditor >> builder: anObject [
builder := anObject
]

{ #category : #accessing }
MABrTextEditor >> elementDescription [
^ elementDescription
]

{ #category : #accessing }
MABrTextEditor >> elementDescription: anObject [
elementDescription := anObject
]

{ #category : #accessing }
MABrTextEditor >> errorBorder [

^ BlBorder paint: Color red width: 1
]

{ #category : #accessing }
MABrTextEditor >> errors [
^ errors ifNil: [ false "MAMultipleErrors new" ]
]

{ #category : #accessing }
MABrTextEditor >> errors: anObject [
errors := anObject
]

{ #category : #accessing }
MABrTextEditor >> hasErrors [
^ self errors" collection isNotNil"
]

{ #category : #accessing }
MABrTextEditor >> header [
^ header
]

{ #category : #accessing }
MABrTextEditor >> header: anObject [
header := anObject
]

{ #category : #accessing }
MABrTextEditor >> initialize [
super initialize.

self
beEditable;
aptitude: MAGtInputFieldAptitude "BrGlamorousEditorAptitude + BrGlamorousInputFieldSpacingAptitude";
geometry: (BlRoundedRectangleGeometry cornerRadius: 4);
vFitContent;
hMatchParent;
text: (self builder textUsing: self elementDescription). "BrGlamorousEditableLabelAptitude" "BrGlamorousButtonExteriorAptitude"

self initializeListeners.
self initializeCompletion
]

{ #category : #accessing }
MABrTextEditor >> initializeCompletion [

self elementDescription propertyAt: #completions ifPresent: [ :comps |
| compStrings compStrat |
compStrings := comps value: self builder object.
compStrat := self builder completionStrategy
completions: (GtPrefixTree withAll: compStrings);
yourself.
(self builder completionControllerClass on: self strategy: compStrat)
showOnTextModification: true;
install ].
]

{ #category : #accessing }
MABrTextEditor >> initializeListeners [

"Handle typed in text"
self editor
when: BrTextEditorModifiedEvent
do: [ :evt | self validate ].

"Handle directly-set text e.g. `text: aText`"
self
when: BrEditorTextChanged
do: [ :evt | self validate ]
]

{ #category : #accessing }
MABrTextEditor >> validate [
[
self elementDescription
writeFromString: self text greaseString
to: self builder memento.
self elementDescription
validate: (self builder memento readUsing: self elementDescription).
self builder showValidationPassFor: self header
]
on: MAReadError, MAMultipleErrors
do: [ "on: MAMultipleErrors
do: [ :err |
self errors: err.
self border: self errorBorder ]":err |
self errors: true.
self builder showWarningFor: self header ]
]
148 changes: 93 additions & 55 deletions source/Magritte-GToolkit/MAElementBuilder.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ MAElementBuilder >> addInputFieldUsing: aDescription [

self
using: aDescription
addInputField: [ self newInputElementUsing: aDescription ]
addInputField: [ :headerElement |
(self newInputElementUsing: aDescription)
header: headerElement;
yourself ]
]

{ #category : #accessing }
Expand Down Expand Up @@ -101,8 +104,7 @@ MAElementBuilder >> calendarButtonFor: editor [
do: [ "calendar date: event date. Redundant because a new calendar will be created on next button click":event |
editor
beEditable;
text: event date mmddyyyy;
acceptEdition.
text: event date mmddyyyy asRopedText.
calendar fireEvent: BrDropdownHideWish new ] ].

^ BrButton new
Expand All @@ -113,10 +115,17 @@ MAElementBuilder >> calendarButtonFor: editor [
yourself
]

{ #category : #private }
MAElementBuilder >> checkIcon [
^ BrGlamorousVectorIcons accept create
background: Color green;
yourself
]

{ #category : #accessing }
MAElementBuilder >> completionControllerClass [

^ completionControllerClass ifNil: [ GtCompletionController ]
^ completionControllerClass ifNil: [ PeGtCompletionController ]
]

{ #category : #accessing }
Expand Down Expand Up @@ -164,13 +173,13 @@ MAElementBuilder >> form [
text: string asRopedText bold;
aptitude: BrGlamorousLabelAptitude;
yourself ].
^ form := BlElement new
^ form := "BlElement"BrVerticalPane new
constraintsDo: [ :c |
c vertical fitContent.
c horizontal matchParent ];
layout: (BlGridLayout horizontal columnCount: 2; cellSpacing: 10);
c horizontal matchParent ]";
layout: (BlGridLayout horizontal columnCount: 2; cellSpacing: 10)"";
addChild: (headerStancil value: 'Field');
addChild: (headerStancil value: 'Current');
addChild: (headerStancil value: 'Current');"
"addChild: (headerStancil value: 'Original');"
yourself.
]
Expand All @@ -188,29 +197,9 @@ MAElementBuilder >> memento [
{ #category : #accessing }
MAElementBuilder >> newInputElementUsing: aDescription [

| editor |
editor := BrEditableLabel new
aptitude:
BrGlamorousEditableLabelAptitude new glamorousRegularFontAndSize;
vFitContent;
hMatchParent;
when: BrEditorAcceptWish do: [ :aWish |
aDescription
writeFromString: aWish text greaseString
to: self memento ];
text: (self textUsing: aDescription).

aDescription propertyAt: #completions ifPresent: [ :comps |
| compStrings compStrat |
compStrings := comps value: self object.
compStrat := self completionStrategy
completions: (GtPrefixTree withAll: compStrings);
yourself.
(self completionControllerClass on: editor strategy: compStrat)
showOnTextModification: true;
install ].

^ editor
^ MABrTextEditor
onDescription: aDescription
forBuilder: self
]

{ #category : #accessing }
Expand Down Expand Up @@ -244,6 +233,32 @@ MAElementBuilder >> presenter [
^ presenter := MABlocContainerPresenter memento: memento
]

{ #category : #accessing }
MAElementBuilder >> showValidationPassFor: headerElement [

headerElement
childNamed: #icon
ifFound: [ :oldIcon |
headerElement
replaceChild: oldIcon
with: self checkIcon
as: #icon ]
ifNone: [ headerElement addChild: self checkIcon as: #icon ]
]

{ #category : #accessing }
MAElementBuilder >> showWarningFor: headerElement [

headerElement
childNamed: #icon
ifFound: [ :oldIcon |
headerElement
replaceChild: oldIcon
with: self warningIcon
as: #icon ]
ifNone: [ headerElement addChild: self warningIcon as: #icon ]
]

{ #category : #accessing }
MAElementBuilder >> textUsing: aDescription [
| valueString |
Expand Down Expand Up @@ -278,34 +293,42 @@ MAElementBuilder >> toolbar [

{ #category : #private }
MAElementBuilder >> using: aDescription addInputField: aValuable [

| labelElement "diffElement" inputElement |
| labelElement "diffElement" inputElement labelString headerElement |
aDescription isVisible ifFalse: [ ^ self ].

inputElement := aValuable value.

labelString := aDescription label.
aDescription isRequired ifTrue: [ labelString := labelString , ' *' ].

labelElement := BrLabel new
text: aDescription label , ':';
text: labelString asRopedText bold;
addEventHandlerOn: BlClickEvent
do: [ :evt | evt target phlow spawnObject: (aDescription read: self object) ];
aptitude: BrGlamorousLabelAptitude.
labelElement constraintsDo: [ :c |

headerElement := BrHorizontalPane new
fitContent;
margin: (BlInsets all: 3);
addChild: labelElement;
yourself.

inputElement := (aValuable cull: headerElement)
margin: (BlInsets top: 2 bottom: 10);
yourself.

labelElement
constraintsDo: [ :c |
c vertical fitContent.
c horizontal fitContent.
c grid vertical alignCenter ].

aDescription isRequired ifTrue: [ self flag: 'unsupported' ].

aDescription hasComment ifTrue: [
self addTooltip: aDescription comment to: labelElement.
self addTooltip: aDescription comment to: inputElement ].

"diffElement := BrEditor new
text: inputElement text copy; ""if we don't copy, diff magically changes as input updates"
"aptitude: BrGlamorousEditorAptitude".

self form addChild: labelElement.
self form addChild: inputElement.
"self form addChild: diffElement."
c grid vertical alignCenter.
c grid horizontal alignRight ].

aDescription hasComment
ifTrue: [ self addTooltip: aDescription comment to: headerElement.
self addTooltip: aDescription comment to: inputElement ]. "diffElement := BrEditor new
text: inputElement text copy; ""if we don't copy, diff magically changes as input updates" "aptitude: BrGlamorousEditorAptitude"

self form addChild: headerElement.
self form addChild: inputElement "self form addChild: diffElement."
]

{ #category : #accessing }
Expand Down Expand Up @@ -358,10 +381,14 @@ MAElementBuilder >> visitDateDescription: aDescription [

self
using: aDescription
addInputField: [
addInputField: [ :headerElement |
| editor calendarButton |
editor := self newInputElementUsing: aDescription.
calendarButton := self calendarButtonFor: editor. "calendar date: event date. Redundant because a new calendar will be created on next button click"
editor := (self newInputElementUsing: aDescription)
header: headerElement;
yourself.
editor constraintsDo: [ :c |
c minWidth: 100 ].
calendarButton := self calendarButtonFor: editor.
BrHorizontalPane new
fitContent;
cellSpacing: 5;
Expand All @@ -387,6 +414,7 @@ MAElementBuilder >> visitDurationDescription: anObject [

{ #category : #'visiting-description' }
MAElementBuilder >> visitElementDescription: aDescription [

self addInputFieldUsing: aDescription
]

Expand Down Expand Up @@ -540,3 +568,13 @@ MAElementBuilder >> visitTokenDescription: anObject [
MAElementBuilder >> visitUrlDescription: anObject [
self visitElementDescription: anObject
]

{ #category : #accessing }
MAElementBuilder >> warningIcon [

^ BlElement new
geometry: BlTriangleGeometry new beTop;
size: 8@8;
background: Color orange;
yourself.
]
5 changes: 5 additions & 0 deletions source/Magritte-GToolkit/MAFieldElement.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Class {
#name : #MAFieldElement,
#superclass : #BrVerticalPane,
#category : #'Magritte-GToolkit'
}
12 changes: 12 additions & 0 deletions source/Magritte-GToolkit/MAGtInputFieldAptitude.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Class {
#name : #MAGtInputFieldAptitude,
#superclass : #BrGlamorousInputFieldSpacingAptitude,
#category : #'Magritte-GToolkit'
}

{ #category : #accessing }
MAGtInputFieldAptitude >> initialize [

super initialize.
self add: BrGlamorousEditorAptitude
]

0 comments on commit 508ce34

Please sign in to comment.