Skip to content

Commit

Permalink
More Shoelace components and examples
Browse files Browse the repository at this point in the history
  • Loading branch information
tatut committed Jan 20, 2024
1 parent 90a9ba2 commit 0402784
Show file tree
Hide file tree
Showing 12 changed files with 548 additions and 71 deletions.
39 changes: 23 additions & 16 deletions src/LiveWeb-Core/HTMLRenderer.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -1677,27 +1677,34 @@ HTMLRenderer >> render: aTag with: attrsOrContent [
{ #category : #rendering }
HTMLRenderer >> renderAttrs: attrs [
"Output all attributes"
(attrs isDictionary ifTrue: [ attrs associations ] ifFalse: [ attrs ])
collect: [ :attr | attributeRenderer render: attr ]
(attrs isDictionary
ifTrue: [ attrs associations ]
ifFalse: [ attrs ])
collect: [ :attr | attributeRenderer render: attr ]
thenDo: [ :attr |
| name value |
name := attr key asString.
value := attr value.
(BooleanAttributes includes: name)
ifTrue: [
"boolean attr, only output the name if value is not false/nil"
(value isNil) | (value = false) ifFalse: [ out nextPut: Character space; nextPutAll: name ]
]
ifFalse: [
('on*' match: name) & ((value isBlock) | (value class = LWScriptCallback))
ifTrue: [ value := value asLWScriptCallback asJS: (ctx registerCallback: value for: component) ].
out nextPut: Character space;
nextPutAll: name;
nextPutAll: '="'.
self escapeHtml: value asString to: out.
out nextPut: $".
]]
ifTrue: [ "boolean attr, only output the name if value is not false/nil"
value isNil | (value = false) ifFalse: [
out
nextPut: Character space;
nextPutAll: name ] ]
ifFalse: [
('on*' match: name)
& (value isBlock | (value class = LWScriptCallback)) ifTrue: [
value := value asLWScriptCallback
asJS: (ctx registerCallback: value for: component)
forComponent: component id ].
out
nextPut: Character space;
nextPutAll: name;
nextPutAll: '="'.
self escapeHtml: value asString to: out.
out nextPut: $" ] ]
]
{ #category : #rendering }
Expand Down
48 changes: 29 additions & 19 deletions src/LiveWeb-Core/LWCustomElement.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,9 @@ LWCustomElement >> initialize [
]

{ #category : #convenience }
LWCustomElement >> on: aLWCustomEventType do: aBlock [
LWCustomElement >> on: aLWCustomEventType do: aBlockOrScript [
eventListeners ifNil: [ eventListeners := OrderedCollection new. ].
eventListeners add: aLWCustomEventType -> aBlock
eventListeners add: aLWCustomEventType -> aBlockOrScript
]

{ #category : #rendering }
Expand Down Expand Up @@ -126,23 +126,33 @@ LWCustomElement >> renderContent: h [

{ #category : #rendering }
LWCustomElement >> renderCustomEvents: h [
eventListeners ifNil: [ ^ nil ].
"Output a script that finds the rendered element and adds listeners to it"
h js: [ :out |
eventListeners do: [ :e |
| cb event callback block |
event := e key.
callback := e value asLWScriptCallback.
callback jsParams: { '[{1}]' format: {(',' join: (event eventFields collect: #value))} }.
block := callback callback.
cb := ctx registerCallback: [ :arr | |evt|
evt := event fromArray: arr.
block value: evt ] for: self.
out << 'this.addEventListener("'; << event type; << '",e=>{console.log(e);';
<< (callback asJS: cb);
<< '});'
].
]

eventListeners ifNil: [ ^ nil ].
"Output a script that finds the rendered element and adds listeners to it"
h js: [ :out |
eventListeners select: [ :e | e value isString ] thenDo: [ :jsEvent |
out << 'this.addEventListener("'; << jsEvent key type; << '",(function(e){';
<< jsEvent value; << '}).bind(_lw.get('; << id asString; << ')));' ].
eventListeners reject: [:e | e value isString ] thenDo: [ :e |
| cb event callback block |
event := e key.
callback := e value asLWScriptCallback.
callback jsParams:
{ ('[{1}]' format:
{ (',' join: (event eventFields collect: #value)) }) }.
block := callback callback.
cb := ctx
registerCallback: [ :arr |
| evt |
evt := event fromArray: arr.
block value: evt ]
for: self.
out
<< 'this.addEventListener("';
<< event type;
<< '",e=>{';
<< (callback asJS: cb forComponent: id);
<< '});' ] ]
]

{ #category : #rendering }
Expand Down
11 changes: 6 additions & 5 deletions src/LiveWeb-Core/LWPage.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -39,26 +39,26 @@ LWPage class >> clientConnectionScript [
get: function(id) {
return document.querySelector("[data-lw=''"+id+"'']");
},
send: function(id, args, debouncems) {
send: function(id, args, debouncems, afterSend) {
if(this.debug) console.log("Sending id:", id, ", args: ", args);
if(!this.connected) {
this.preOpenQueue.push({id: id, args: args});
} else {
if(debouncems === undefined) {
this._send(id,args);
if(debouncems === undefined || debouncems === 0) {
this._send(id,args, afterSend);
} else {
var tid = this.debounceTimeout[id];
if(tid !== undefined) {
window.clearTimeout(tid);
}
this.debounceTimeout[id] = window.setTimeout(
function() { _lw._send(id,args); },
function() { _lw._send(id,args,afterSend); },
debouncems
);
}
}
},
_send: function(id,args) {
_send: function(id,args,afterSend) {
if(this.type === "sse") {
var l = window.location;
fetch(l.protocol+"//"+l.host+this.cpath+"?id="+this.cid,
Expand All @@ -71,6 +71,7 @@ LWPage class >> clientConnectionScript [
} else {
console.err("Unknown connection type: ", this.type);
}
if(typeof(afterSend)==="function") afterSend();
},
onopen: function(e) {
this.connected = true;
Expand Down
1 change: 1 addition & 0 deletions src/LiveWeb-Core/LWPageConnection.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ LWPageConnection >> rerenderComponent: component [
]
on: Exception
do: [ :e |
e inspect.
LWLogEvent error: ('Error in component: {1}, message: {2}' format: { component printString. e messageText }).
ws close. ].
]
Expand Down
51 changes: 36 additions & 15 deletions src/LiveWeb-Core/LWScriptCallback.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,41 @@ Class {
'condition',
'jsParams',
'debounceMs',
'preventDefault'
'preventDefault',
'thenJS'
],
#category : #'LiveWeb-Core'
}

{ #category : #'as yet unclassified' }
LWScriptCallback >> afterSend: id [
thenJS ifNil: [ ^ 'null' ].

]

{ #category : #'as yet unclassified' }
LWScriptCallback >> afterSend: js forComponent: id [
thenJS ifNil: [ ^ nil ].
js << ',(function(){';
<< thenJS;
<< '}).bind(_lw.get("';
<< id asString;
<< '"))'


]

{ #category : #converting }
LWScriptCallback >> asJS: cb [
LWScriptCallback >> asJS: cb forComponent: id [
^ String streamContents: [ :js |
preventDefault ifTrue: [ js nextPutAll:
'window.event.preventDefault();' ].
condition ifNotNil: [ js nextPutAll: 'if('; nextPutAll: condition; nextPutAll: ') ' ].
js nextPutAll: '_lws(';
nextPutAll: cb asString;
nextPutAll:',['.
jsParams doWithIndex: [ :p :i |
i > 1 ifTrue: [ js nextPut: $, ].
js nextPutAll: p.
].
js nextPutAll: ']'.
debounceMs ifNotNil: [ js nextPut: $,; nextPutAll: debounceMs asString ].
js nextPut: $) ]
preventDefault ifTrue: [ js << 'window.event.preventDefault();' ].
condition ifNotNil: [ js << 'if(!('; << condition; << ')) return;' ].
js << '_lws('; << cb asString; << ',[';
<< (',' join: jsParams);
<< '],';
<< (debounceMs ifNil: [ '0' ] ifNotNil: [ debounceMs asString]).
self afterSend: js forComponent: id.
js << ')' ]
]

{ #category : #converting }
Expand Down Expand Up @@ -102,6 +116,13 @@ LWScriptCallback >> preventDefault: prevent [
preventDefault := prevent
]

{ #category : #accessing }
LWScriptCallback >> thenJS: aScript [
"Set JS code to run immediately after sending the callback.
JavaScript 'this' is bound to the current component."
thenJS := aScript
]

{ #category : #evaluating }
LWScriptCallback >> valueWithArguments: args [
^ callback valueWithArguments: args
Expand Down
41 changes: 28 additions & 13 deletions src/LiveWeb-Core/LWWindowListener.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -32,22 +32,37 @@ LWWindowListener >> event: anObject [

{ #category : #rendering }
LWWindowListener >> renderOn: h [

| firstRender callbackId |
callbackId := (ctx registerCallback: callback for: self).
callbackId := ctx registerCallback: callback for: self.
firstRender := listenerId isNil.
listenerId := '_lwlisten',id asString.
h script: [
h streamContents: [ :out |
"Output script that adds the listener when window is ready.
listenerId := '_lwlisten' , id asString.
h script: [
h streamContents: [ :out | "Output script that adds the listener when window is ready.
The listener is removed on unmount."
firstRender
ifTrue: [ out
<< listenerId; << ' = {id: '; << callbackId asString; << ', cb: ';
<< '(event) => '; << (callback asLWScriptCallback asJS: listenerId,'.id'); <<' };';
<< 'window.addEventListener("'; << event; << '", '; << listenerId; << '.cb);' ]
ifFalse: [ out
<< listenerId; << '.id = '; << callbackId asString; << ';' ]
]]
firstRender
ifTrue: [
out
<< listenerId;
<< ' = {id: ';
<< callbackId asString;
<< ', cb: ';
<< '(event) => ';
<< (callback asLWScriptCallback
asJS: listenerId , '.id'
forComponent: id);
<< ' };';
<< 'window.addEventListener("';
<< event;
<< '", ';
<< listenerId;
<< '.cb);' ]
ifFalse: [
out
<< listenerId;
<< '.id = ';
<< callbackId asString;
<< ';' ] ] ]
]

{ #category : #'component lifecycle' }
Expand Down
2 changes: 1 addition & 1 deletion src/LiveWeb-Developer/LWDevMain.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ LWDevMain >> initialize [
vertical: false;
start: splitListing;
end: classView.
spotterDialog := LWDevSpotter new.
spotterDialog := LWDevSpotter new classView: classView.


]
Expand Down
15 changes: 13 additions & 2 deletions src/LiveWeb-Developer/LWDevSpotter.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ Class {
#superclass : #LWComponent,
#instVars : [
'dialog',
'menu'
'menu',
'classView'
],
#category : #'LiveWeb-Developer'
}
Expand All @@ -14,6 +15,12 @@ LWDevSpotter >> children [

]

{ #category : #accessing }
LWDevSpotter >> classView: anObject [

classView := anObject
]

{ #category : #'typing/selecting keys' }
LWDevSpotter >> find: aString [
| find classes classMenu |
Expand All @@ -22,7 +29,11 @@ LWDevSpotter >> find: aString [
find isEmpty
ifTrue: [ classMenu add: (SlMenuItem new disabled: true; add: 'Type part of class name to search'; yourself) ]
ifFalse: [
classMenu on: SlSelectEvent do: [ :e | e inspect ]. "selected something!"
classMenu
on: SlSelectEvent
do: ([ :e | classView cls: (self class environment at: e selectedItemValue asSymbol) ]
"close dialog after"
asLWScriptCallback thenJS: 'this.parentNode.parentNode.hide()').
find := '*{1}*' format: { aString }.
classes := (Smalltalk globals allClasses select: [ :c | find match: c name ]) takeFirst: 20.
classes
Expand Down
30 changes: 30 additions & 0 deletions src/LiveWeb-Shoelace/SlAlert.class.st
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,36 @@ SlAlert class >> elementSlots [
^ #(icon)
]

{ #category : #'as yet unclassified' }
SlAlert class >> simpleAlertWithTextExample [
<lwExample: 'Simple alert with text'>
^ self new add: 'I''m an alert and I''m okay!'; open: true
]

{ #category : #'as yet unclassified' }
SlAlert class >> toastExample [
<lwExample: 'Example of moving alert to toast'>
^ LWContainer new
add: (self new variant: 'success'; className: 'example-toast';
icon: (SlIcon new name: 'lungs');
duration: 3000;
add: 'to your continued health!'; yourself);
add: (SlButton new
on: SlClickEvent do: 'document.querySelector(".example-toast").toast()';
add: 'toast it'; yourself);
yourself
]

{ #category : #'as yet unclassified' }
SlAlert class >> warningAlertWithIconExample [
<lwExample: 'Warning and icon'>
^ self new
add: 'Something alarming just happened!';
variant: 'warning';
icon: (SlIcon new name: 'exclamation-triangle');
open: true.
]

{ #category : #'as yet unclassified' }
SlAlert >> closable [
^ self attrAt: 'closable'
Expand Down
Loading

0 comments on commit 0402784

Please sign in to comment.