BrowserSm Examples
This page provides practical examples of using BrowserSm for various tasks, from basic VM usage to complex browser applications.
Table of Contents
- Basic VM Usage
- Smalltalk Development
- Browser Application Examples
- Web Page Scripting
- Custom Extensions
- Performance Examples
Basic VM Usage
Loading and Running Images
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // Load a Squeak image in the browser
const vm = new BrowserSm.VM();
// Load from URL
await vm.loadImage('images/squeak6.0-basic.image');
// Load from file input
const fileInput = document.getElementById('imageFile');
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
const arrayBuffer = await file.arrayBuffer();
await vm.loadImage(arrayBuffer);
});
// Start execution
vm.run();
|
Interacting with Smalltalk from JavaScript
1
2
3
4
5
6
7
8
9
10
11
12
| // Execute Smalltalk code from JavaScript
const result = await vm.evaluate('2 + 3');
console.log(result); // 5
// Create Smalltalk objects
const point = await vm.evaluate('Point x: 10 y: 20');
const x = await vm.evaluate('point x', { point });
console.log(x); // 10
// Call Smalltalk methods
const browser = await vm.evaluate('BSBrowser new');
await vm.evaluate('browser open', { browser });
|
Smalltalk Development
Creating a Simple Application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| "Define a simple calculator application"
Object subclass: #Calculator
instanceVariableNames: 'display accumulator operation'
classVariableNames: ''
poolDictionaries: ''
category: 'Examples-Calculator'.
Calculator>>initialize
super initialize.
display := '0'.
accumulator := 0.
operation := nil.
Calculator>>enterDigit: digit
display := (display = '0')
ifTrue: [ digit asString ]
ifFalse: [ display , digit asString ].
Calculator>>enterOperation: op
accumulator := display asNumber.
operation := op.
display := '0'.
Calculator>>calculate
| result |
result := operation
ifNil: [ accumulator ]
ifNotNil: [
accumulator perform: operation with: display asNumber
].
display := result printString.
accumulator := result.
operation := nil.
"Usage"
calc := Calculator new.
calc enterDigit: 5.
calc enterOperation: #+.
calc enterDigit: 3.
calc calculate. "Returns '8'"
|
Building a Morphic UI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
| "Create a simple window with buttons"
Morph subclass: #SimpleWindow
instanceVariableNames: 'buttons textArea'
classVariableNames: ''
poolDictionaries: ''
category: 'Examples-UI'.
SimpleWindow>>initialize
super initialize.
self extent: 300@200.
self color: Color lightGray.
self setupUI.
SimpleWindow>>setupUI
"Add text area"
textArea := PluggableTextMorph new.
textArea position: 10@10.
textArea extent: 280@120.
self addMorph: textArea.
"Add buttons"
buttons := OrderedCollection new.
#('Save' 'Load' 'Clear') withIndexDo: [ :label :index |
| button |
button := SimpleButtonMorph new.
button label: label.
button position: (10 + (index - 1 * 70))@140.
button extent: 60@30.
button target: self.
button actionSelector: ('handle', label) asSymbol.
buttons add: button.
self addMorph: button.
].
SimpleWindow>>handleSave
FileDirectory default writeFileNamed: 'data.txt' contents: textArea text.
SimpleWindow>>handleLoad
| content |
content := FileDirectory default contentsOfFileNamed: 'data.txt'.
textArea setText: content.
SimpleWindow>>handleClear
textArea setText: ''.
"Open the window"
SimpleWindow new openInWorld.
|
Browser Application Examples
Custom HTML Element
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| "Create a custom progress bar element"
BSDOMElement subclass: #BSProgressBar
instanceVariableNames: 'value maximum animated'
classVariableNames: ''
poolDictionaries: ''
category: 'Examples-CustomElements'.
BSProgressBar class>>tagName
^ 'progress-bar'
BSProgressBar>>initialize
super initialize.
self tagName: 'progress-bar'.
value := 0.
maximum := 100.
animated := false.
self createStructure.
BSProgressBar>>createStructure
"Create internal DOM structure"
| container bar |
container := BSDOMElement tag: 'div'.
container setAttribute: 'class' value: 'progress-container'.
bar := BSDOMElement tag: 'div'.
bar setAttribute: 'class' value: 'progress-bar-fill'.
container appendChild: bar.
self appendChild: container.
BSProgressBar>>value: newValue
value := newValue min: maximum max: 0.
self updateDisplay.
BSProgressBar>>updateDisplay
| percentage bar |
percentage := (value / maximum * 100) rounded.
bar := self querySelector: '.progress-bar-fill'.
bar style setProperty: 'width' value: percentage printString , '%'.
"Register the custom element"
BSHTMLParser registerCustomElement: 'progress-bar' class: BSProgressBar.
"Use in HTML"
"<progress-bar id='myProgress' max='100'></progress-bar>"
|
Browser Plugin Example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| "Create a simple bookmark manager plugin"
Object subclass: #BSBookmarkManager
instanceVariableNames: 'bookmarks browser'
classVariableNames: ''
poolDictionaries: ''
category: 'Examples-Plugins'.
BSBookmarkManager>>initialize
super initialize.
bookmarks := OrderedCollection new.
self loadBookmarks.
BSBookmarkManager>>install: aBrowser
browser := aBrowser.
browser registerPlugin: self.
self addToolbarButton.
BSBookmarkManager>>addToolbarButton
| button |
button := BSToolbarButton new.
button label: 'Bookmarks'.
button action: [ self openBookmarkPanel ].
browser toolbar addButton: button.
BSBookmarkManager>>addBookmark: url title: title
bookmarks add: (Dictionary new
at: 'url' put: url;
at: 'title' put: title;
at: 'timestamp' put: DateAndTime now;
yourself).
self saveBookmarks.
BSBookmarkManager>>openBookmarkPanel
| panel list |
panel := BSModalPanel new.
panel title: 'Bookmarks'.
list := BSListMorph new.
bookmarks do: [ :bookmark |
| item |
item := BSListItem new.
item label: (bookmark at: 'title').
item action: [
browser navigateTo: (bookmark at: 'url').
panel close.
].
list addItem: item.
].
panel addMorph: list.
panel open.
|
Web Page Scripting
DOM Manipulation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| <!DOCTYPE html>
<html>
<head>
<title>Smalltalk DOM Example</title>
</head>
<body>
<h1 id="title">Hello, World!</h1>
<button id="changeColor">Change Color</button>
<div id="content"></div>
<script type="text/smalltalk">
"Get DOM elements"
title := document getElementById: 'title'.
button := document getElementById: 'changeColor'.
content := document getElementById: 'content'.
"Create color array"
colors := #('red' 'blue' 'green' 'purple' 'orange').
currentColor := 1.
"Add event listener"
button addEventListener: 'click' handler: [
| color |
color := colors at: currentColor.
title style setProperty: 'color' value: color.
currentColor := currentColor + 1.
currentColor > colors size ifTrue: [ currentColor := 1 ].
"Update content"
content innerHTML: '<p>Color changed to: ' , color , '</p>'.
].
"Initial setup"
console log: 'Smalltalk script loaded successfully!'.
</script>
</body>
</html>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
| <!DOCTYPE html>
<html>
<body>
<form id="contactForm">
<input type="text" id="name" placeholder="Name" required>
<input type="email" id="email" placeholder="Email" required>
<textarea id="message" placeholder="Message" required></textarea>
<button type="submit">Send</button>
</form>
<script type="text/smalltalk">
"Form validation and submission"
form := document getElementById: 'contactForm'.
form addEventListener: 'submit' handler: [ :event |
| name email message valid |
"Prevent default submission"
event preventDefault.
"Get form values"
name := (document getElementById: 'name') value trimBoth.
email := (document getElementById: 'email') value trimBoth.
message := (document getElementById: 'message') value trimBoth.
"Validate"
valid := true.
name isEmpty ifTrue: [
self showError: 'Name is required'.
valid := false.
].
(email includes: $@) ifFalse: [
self showError: 'Valid email is required'.
valid := false.
].
message isEmpty ifTrue: [
self showError: 'Message is required'.
valid := false.
].
"Submit if valid"
valid ifTrue: [
self submitForm: (Dictionary new
at: 'name' put: name;
at: 'email' put: email;
at: 'message' put: message;
yourself).
].
].
"Helper methods"
submitForm: data
fetch url: '/api/contact'
method: 'POST'
headers: (Dictionary new at: 'Content-Type' put: 'application/json'; yourself)
body: (JSON stringify: data)
then: [ :response |
response ok ifTrue: [
self showSuccess: 'Message sent successfully!'
] ifFalse: [
self showError: 'Failed to send message'.
].
]
catch: [ :error |
console error: error.
self showError: 'Network error occurred'.
].
showError: message
| errorDiv |
errorDiv := document createElement: 'div'.
errorDiv className: 'error'.
errorDiv textContent: message.
form appendChild: errorDiv.
showSuccess: message
| successDiv |
successDiv := document createElement: 'div'.
successDiv className: 'success'.
successDiv textContent: message.
form appendChild: successDiv.
</script>
</body>
</html>
|
Canvas Graphics
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
| <!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="400" height="300"></canvas>
<button id="animate">Start Animation</button>
<script type="text/smalltalk">
"Canvas drawing and animation"
canvas := document getElementById: 'myCanvas'.
ctx := canvas getContext: '2d'.
button := document getElementById: 'animate'.
"Animation state"
animating := false.
ballX := 50.
ballY := 50.
ballSpeedX := 2.
ballSpeedY := 3.
ballRadius := 20.
"Drawing method"
drawFrame := [
"Clear canvas"
ctx clearRect: 0 y: 0 width: 400 height: 300.
"Draw ball"
ctx fillStyle: 'blue'.
ctx beginPath.
ctx arc: ballX y: ballY radius: ballRadius startAngle: 0 endAngle: Float pi * 2.
ctx fill.
"Update position"
ballX := ballX + ballSpeedX.
ballY := ballY + ballSpeedY.
"Bounce off walls"
(ballX + ballRadius > 400 or: [ ballX - ballRadius < 0 ]) ifTrue: [
ballSpeedX := ballSpeedX negated.
].
(ballY + ballRadius > 300 or: [ ballY - ballRadius < 0 ]) ifTrue: [
ballSpeedY := ballSpeedY negated.
].
].
"Animation loop"
animate := [
animating ifTrue: [
drawFrame value.
requestAnimationFrame: animate.
].
].
"Button handler"
button addEventListener: 'click' handler: [
animating := animating not.
button textContent: (animating
ifTrue: [ 'Stop Animation' ]
ifFalse: [ 'Start Animation' ]).
animating ifTrue: [ animate value ].
].
"Initial draw"
drawFrame value.
</script>
</body>
</html>
|
Custom Extensions
CSS Engine Extension
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
| "Add support for CSS Grid Layout"
BSCSSLayoutEngine subclass: #BSCSSGridLayout
instanceVariableNames: 'gridContainer gridItems columnTracks rowTracks'
classVariableNames: ''
poolDictionaries: ''
category: 'Examples-CSSExtensions'.
BSCSSGridLayout>>layout: element inBox: containerBox
"Implement CSS Grid layout algorithm"
self setupGridContainer: element.
self setupGridTracks.
self positionGridItems.
BSCSSGridLayout>>setupGridContainer: element
| gridTemplate |
gridContainer := element.
gridItems := element children select: [ :child |
child computedStyle display ~= 'none'
].
"Parse grid-template-columns and grid-template-rows"
gridTemplate := element computedStyle getProperty: 'grid-template-columns'.
self parseGridTemplate: gridTemplate for: #columns.
BSCSSGridLayout>>parseGridTemplate: template for: axis
"Parse CSS grid template syntax like '1fr 2fr 100px'"
| tracks |
tracks := template splitOn: ' '.
axis = #columns
ifTrue: [ columnTracks := tracks ]
ifFalse: [ rowTracks := tracks ].
BSCSSGridLayout>>positionGridItems
gridItems withIndexDo: [ :item :index |
| column row |
column := (index - 1) \\ columnTracks size + 1.
row := (index - 1) // columnTracks size + 1.
self positionItem: item inColumn: column row: row.
].
|
Browser Security Extension
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| "Content Security Policy implementation"
Object subclass: #BSContentSecurityPolicy
instanceVariableNames: 'directives violations browser'
classVariableNames: ''
poolDictionaries: ''
category: 'Examples-Security'.
BSContentSecurityPolicy>>initialize
super initialize.
directives := Dictionary new.
violations := OrderedCollection new.
BSContentSecurityPolicy>>parseHeader: headerValue
"Parse CSP header: 'default-src 'self'; script-src 'unsafe-eval'"
| parts |
parts := headerValue splitOn: ';'.
parts do: [ :part |
| directive sources |
part := part trimBoth.
directive := (part splitOn: ' ') first.
sources := (part splitOn: ' ') allButFirst.
directives at: directive put: sources.
].
BSContentSecurityPolicy>>checkScriptExecution: scriptElement
"Validate script execution against CSP"
| scriptSrc allowedSources |
scriptSrc := scriptElement getAttribute: 'src'.
allowedSources := directives at: 'script-src' ifAbsent: [
directives at: 'default-src' ifAbsent: [ #() ]
].
^ self isSourceAllowed: scriptSrc in: allowedSources.
BSContentSecurityPolicy>>isSourceAllowed: source in: allowedSources
allowedSources do: [ :allowed |
allowed = '''self''' ifTrue: [ ^ self isSameOrigin: source ].
allowed = '''unsafe-eval''' ifTrue: [ ^ true ].
(source beginsWith: allowed) ifTrue: [ ^ true ].
].
^ false.
BSContentSecurityPolicy>>reportViolation: violation
violations add: violation.
console warn: 'CSP violation: ', violation description.
|
Optimized DOM Updates
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
| "Batch DOM updates for better performance"
Object subclass: #BSDOMBatcher
instanceVariableNames: 'pendingUpdates scheduledUpdate'
classVariableNames: ''
poolDictionaries: ''
category: 'Examples-Performance'.
BSDOMBatcher>>initialize
super initialize.
pendingUpdates := OrderedCollection new.
scheduledUpdate := false.
BSDOMBatcher>>queueUpdate: updateBlock for: element
pendingUpdates add: (Dictionary new
at: 'element' put: element;
at: 'update' put: updateBlock;
yourself).
self scheduleFlush.
BSDOMBatcher>>scheduleFlush
scheduledUpdate ifFalse: [
scheduledUpdate := true.
requestAnimationFrame: [ self flush ].
].
BSDOMBatcher>>flush
"Apply all pending updates in a single frame"
pendingUpdates do: [ :update |
(update at: 'update') value: (update at: 'element').
].
pendingUpdates removeAll.
scheduledUpdate := false.
"Usage"
batcher := BSDOMBatcher new.
"Instead of immediate updates:"
element style setProperty: 'color' value: 'red'.
element style setProperty: 'fontSize' value: '16px'.
"Batch them:"
batcher queueUpdate: [ :el |
el style setProperty: 'color' value: 'red'.
el style setProperty: 'fontSize' value: '16px'.
] for: element.
|
Memory-Efficient Large Lists
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| "Virtual scrolling for large datasets"
Morph subclass: #BSVirtualList
instanceVariableNames: 'items visibleItems itemHeight containerHeight scrollTop'
classVariableNames: ''
poolDictionaries: ''
category: 'Examples-Performance'.
BSVirtualList>>initialize
super initialize.
items := OrderedCollection new.
visibleItems := OrderedCollection new.
itemHeight := 30.
containerHeight := 300.
scrollTop := 0.
self setupScrolling.
BSVirtualList>>setItems: itemCollection
items := itemCollection.
self updateVisibleItems.
BSVirtualList>>updateVisibleItems
| startIndex endIndex visibleCount |
visibleCount := containerHeight // itemHeight + 2. "Buffer items"
startIndex := scrollTop // itemHeight + 1.
endIndex := (startIndex + visibleCount) min: items size.
visibleItems removeAll.
startIndex to: endIndex do: [ :index |
visibleItems add: (items at: index).
].
self renderVisibleItems.
BSVirtualList>>renderVisibleItems
self removeAllMorphs.
visibleItems withIndexDo: [ :item :index |
| itemMorph y |
itemMorph := self createItemMorph: item.
y := (scrollTop // itemHeight + index - 1) * itemHeight.
itemMorph position: 0@y.
self addMorph: itemMorph.
].
BSVirtualList>>handleScroll: scrollEvent
scrollTop := scrollEvent scrollTop.
self updateVisibleItems.
|
These examples demonstrate the flexibility and power of BrowserSm, showing how the VM enables both traditional Squeak development and innovative web applications using Smalltalk throughout the entire stack.