Skip to content

Commit

Permalink
Experimental support for HTML renderOn methods in dev panel.
Browse files Browse the repository at this point in the history
  • Loading branch information
tatut committed Feb 1, 2024
1 parent 4bef79d commit 7f2d6da
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 26 deletions.
21 changes: 21 additions & 0 deletions src/LiveWeb-Core/LWPartsComponent.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"
I am a component that has multiple render methods, which each make up a child component.
This makes it convenient to have separately updatable child components, while keeping the rendering
in the same class.
When initialized, I will create a dictionary of children that map selector to a newly created
LWBlockContainer component. Each child's render must output a single element.
Each child component render method must have an <lwPart> pragma attached.
There is no need to implement children method, it is automatic.
"
Class {
#name : #LWPartsComponent,
#superclass : #LWComponent,
#instVars : [
'parts'
],
#category : #'LiveWeb-Core'
}
4 changes: 3 additions & 1 deletion src/LiveWeb-Developer-Tests/LWDevHTMLCompilerTest.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,7 @@ LWDevHTMLCompilerTest >> testSplitMustache [
self assert: (c splitMustache: 'I have no code blocks')
equals: #('''I have no code blocks''') asOrderedCollection.
self assert: (c splitMustache: '')
equals: OrderedCollection empty
equals: OrderedCollection empty.
self assert: (c splitMustache: '{{foo bar}}')
equals: #('foo bar') asOrderedCollection
]
46 changes: 36 additions & 10 deletions src/LiveWeb-Developer/LWDevHTMLCompiler.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,24 @@ LWDevHTMLCompiler class >> isHTMLMethod: m [
]

{ #category : #compiling }
LWDevHTMLCompiler >> compile: parsedHtml [
LWDevHTMLCompiler >> compile: parsed [
"Compile parsed code without method header line or pragma."
^String streamContents: [ :c |
code := c.
self compileTag: parsedHtml ]
self compileTag: parsed ]
]

{ #category : #compiling }
LWDevHTMLCompiler >> compile: sourceAndParsed as: selectorWithArgs [
"Compile an HTML method from the given dictionary that contains the HTML source
and the parsed tree and a selector line."
^String streamContents: [ :c |
code := c.
rendererName := 'h'. "FIXME: actually parse selector line"
code << ('{1}
<lwHTML: ''{2}''>
' format: { selectorWithArgs. self escapeQuotes: (sourceAndParsed at: 'src') }).
self compileTag: (sourceAndParsed at: 'parsed') ]
]

{ #category : #compiling }
Expand All @@ -77,7 +91,7 @@ LWDevHTMLCompiler >> compileAttr: attrCode [
| blocks |
blocks := self splitMustache: attrCode.
"If there is only 1 block, return that as is."
blocks size = 1 ifTrue: [ ^ blocks first ].
blocks size = 1 ifTrue: [ ^ ('({1})' format: blocks) ].
"If there are multiple blocks, emit code to append string."
^ String streamContents: [ :out |
out << '(String streamContents: [ :__lw_attr | '.
Expand Down Expand Up @@ -153,12 +167,24 @@ LWDevHTMLCompiler >> compileTag: tagName attrs: tagAttrs with: content [
code
<< ''''; << attr key; << ''' -> ';
<< (self compileAttr: attr value);
<< ' ' ].
code << ' } with: [ '.
content do: [ :c | self compileTag: c ].
code
<< '].';
cr
<< '. ' ].
code << ' }'.
content ifNotEmpty: [
code << ' with: [ '.
content do: [ :c | self compileTag: c ].
code << ']'
].
code << '.'; cr.

]

{ #category : #'as yet unclassified' }
LWDevHTMLCompiler >> escapeQuotes: str [
^String streamContents: [ :out |
str do: [ :ch |
out << (ch = $' ifTrue: [ '''''' ] ifFalse: [ ch ])
]
]
]
{ #category : #accessing }
Expand All @@ -182,7 +208,7 @@ LWDevHTMLCompiler >> splitMustache: text [
ifAbsent: [ Error signal: 'No ending }} found in code block' ].
blocks := OrderedCollection new.
"Add the text before the first code block, if any"
codeStart > 0 ifTrue: [ blocks add: ('''{1}''' format: { text copyFrom: 1 to: codeStart-1 }) ].
codeStart > 1 ifTrue: [ blocks add: ('''{1}''' format: { text copyFrom: 1 to: codeStart-1 }) ].
"Add the extracted code block"
blocks add: (text copyFrom: codeStart+2 to: codeEnd-1).
"Recursively handle text after code block"
Expand Down
23 changes: 14 additions & 9 deletions src/LiveWeb-Developer/LWDevMain.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,31 @@ LWDevMain >> classViewFor: className [

{ #category : #compiling }
LWDevMain >> compile: className side: side source: source [

| target methodName cls |
cls := self class environment at: className asSymbol .
cls := self class environment at: className asSymbol.
target := side = 'instance'
ifTrue: [ cls ]
ifFalse: [ cls class ].
methodName := target compile: source.
"FIXME: should listen to method announcement? should only change/add method that was compiled"
"methods child: self createMethodListing ."
(self methodsViewFor: className)
update: side = 'instance' method: methodName.
(self classViewFor: className) methodChanged: methodName.


"if source is a dictionary, then compile this as HTML"
methodName := source isDictionary
ifTrue: [ target compile: (LWDevHTMLCompiler new compile: source as: 'renderOn: h') ]
ifFalse: [ target compile: source ].

(self methodsViewFor: className)
update: side = 'instance'
method: methodName.
(self classViewFor: className) methodChanged: methodName
]

{ #category : #initialization }
LWDevMain >> initialize [
| classSplit |
super initialize.
export := LWExportJS new
export: #lwCompileMethod -> [ :cname :side :source | self compile: cname side: side source: source ].
export: #lwCompileMethod -> [ :cname :side :source |
self compile: cname side: side source: source ].
classView := LWDevClassView new.
classMethodsView := LWDevClassMethodsView new.
classSplit := SlSplitPanel new
Expand Down
28 changes: 26 additions & 2 deletions src/LiveWeb-Developer/LWDevMethodView.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@ LWDevMethodView >> cls: aClass [
cls := aClass
]

{ #category : #'as yet unclassified' }
LWDevMethodView >> codeFormat [
^ self isHTML
ifTrue: ['html']
ifFalse: ['st']
]

{ #category : #'as yet unclassified' }
LWDevMethodView >> codeSource [
^ self isHTML
ifTrue: [ self htmlSource ]
ifFalse: [ method sourceCode ]
]

{ #category : #'as yet unclassified' }
LWDevMethodView >> htmlSource [
^ (method pragmaAt: #lwHTML:) argumentAt: 1
]

{ #category : #testing }
LWDevMethodView >> isHTML [
^ (method pragmaAt: #lwHTML:) isNotNil
]

{ #category : #testing }
LWDevMethodView >> isInstance [
^ instance
Expand Down Expand Up @@ -64,8 +88,8 @@ LWDevMethodView >> renderOn: h [
];
pre: { 'data-side' -> (instance ifTrue: ['instance'] ifFalse:['class']).
'data-class' -> cls name.
#onclick -> 'initEditor()'.
#onclick -> ('initEditor(''{1}'')' format: {self codeFormat}).
#class -> 'codeEditor'. #style -> 'max-height: 40vh; overflow-y: scroll;' }
with: [ h renderContent: method sourceCode ].
with: [ h renderContent: self codeSource ].
]
]
12 changes: 9 additions & 3 deletions src/LiveWeb-Developer/LWDevPage.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,27 @@ LWDevPage >> head: _args [
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.1/ace.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.23.1/ext-language_tools.min.js"></script>
<script>function initEditor() {
<script>function initEditor(mode) {
const e = window.event.target;
if(e.tagName != "PRE" || e.classList.contains("ace_editor")) return;
console.log(e.classList);
const side = e.dataset.side;
const cls = e.dataset.class;
const sel = e.dataset.selector;
const html = mode === "html";
e.style.height = Math.max(100, e.clientHeight) + "px";
let editor = ace.edit(e);
editor.session.setMode("ace/mode/text");
editor.session.setMode("ace/mode/"+(html?"html":"text"));
editor.setTheme("ace/theme/tomorrow");
editor.resize();
editor.commands.addCommand({
name: "save",
bindKey: {win: "Ctrl-s", mac: "Command-s"},
exec: function(editor) { window.event.preventDefault(); lwCompileMethod(cls, side, editor.session.getValue()); },
exec: function(editor) {
window.event.preventDefault();
const txt = editor.session.getValue();
lwCompileMethod(cls, side, html ? htmlToJson(txt) : txt);
},
readOnly: true});
}';
<< (LWDevHTMLCompiler htmlToJsonScript);
Expand Down
3 changes: 2 additions & 1 deletion src/LiveWeb-Examples/LWExampleMain.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ LWExampleMain >> state: aDictionary [
'multi-counter' -> LWMultiCounter .
'crud' -> LWCrudExample.
'typeahead' -> LWTypeAheadExample.
'tree' -> LWTreeExample } asDictionary at: (state at: 'example').
'tree' -> LWTreeExample.
'todo' -> LWTodoExample } asDictionary at: (state at: 'example').
"create new instance of example component"
component := ex new.
ex = LWWordle ifTrue: [ component configure: (state at: 'query') ]. "for tests"
Expand Down
66 changes: 66 additions & 0 deletions src/LiveWeb-Examples/LWTodoExample.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
Class {
#name : #LWTodoExample,
#superclass : #LWComponent,
#instVars : [
'items'
],
#category : #'LiveWeb-Examples'
}

{ #category : #'as yet unclassified' }
LWTodoExample class >> todoExample [
<lwExample: 'Todo with some items'>
^ self new
item: (LWTodoItem new label: 'create example'; beComplete);
item: (LWTodoItem new label: 'show changing HTML rendering');
item: (LWTodoItem new label: 'profit!')
]

{ #category : #initialization }
LWTodoExample >> initialize [
super initialize.
items := OrderedCollection new.
]

{ #category : #accessing }
LWTodoExample >> item: aTodoItem [
items add: aTodoItem.
self changed.
]

{ #category : #'as yet unclassified' }
LWTodoExample >> itemClass: item [
^ item complete ifTrue: [ 'complete' ] ifFalse: [ 'incomplete' ]
]

{ #category : #rendering }
LWTodoExample >> renderOn: h [
<lwHTML: '<div class="todos">
<ul>
<li lw:repeat="item items" class="todo {{self itemClass: item}}">
<input type="checkbox" checked="{{item complete}}"> {{item label}}
</li>
</ul>
todos here</div>'>
h div: { 'class' -> ('todos'). } with: [ h renderContent: ('
').
h ul: { } with: [ h renderContent: ('
').
(items) doWithIndex: [ :item :__lw_index |
h li: { 'class' -> (String streamContents: [ :__lw_attr | __lw_attr << ('todo '). __lw_attr << (self itemClass: item). ]). } with: [ h renderContent: ('
').
h input: { 'checked' -> (item complete). 'type' -> ('checkbox'). }.
h renderContent: (' ').
h renderContent: (item label).
h renderContent: ('
').
].
].
h renderContent: ('
').
].
h renderContent: ('
todos here').
].

]
45 changes: 45 additions & 0 deletions src/LiveWeb-Examples/LWTodoItem.class.st
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
Class {
#name : #LWTodoItem,
#superclass : #Object,
#instVars : [
'label',
'complete'
],
#category : #'LiveWeb-Examples'
}

{ #category : #API }
LWTodoItem >> beComplete [
self complete: true
]

{ #category : #accessing }
LWTodoItem >> complete [

^ complete
]

{ #category : #accessing }
LWTodoItem >> complete: anObject [

complete := anObject
]

{ #category : #initialization }
LWTodoItem >> initialize [
super initialize.
complete := false.

]

{ #category : #accessing }
LWTodoItem >> label [

^ label
]

{ #category : #accessing }
LWTodoItem >> label: anObject [

label := anObject
]

0 comments on commit 7f2d6da

Please sign in to comment.