scratch0-5 / demo /squeakjs.changes
soiz1's picture
Upload folder using huggingface_hub
8f3f8db verified
Smalltalk getSystemAttribute: 2.! !SystemDictionary methodsFor: 'as yet unclassified' stamp: 'bf 11/21/2014 16:46' prior: 0! startUp | document | self openSourceFiles. document := self getSystemAttribute: 2. document ifNil: [^self]. (FileStream readOnlyFileNamed: document) fileIn. ! ! ----QUIT----(21 November 2014 4:47:26 pm ) priorSource: 2326! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "To reverse the display..." Smalltalk reverseDisplay! "To reverse the display..." Smalltalk reverseDisplay! "To reverse the display..." Smalltalk reverseDisplay! "To reverse the display..." Smalltalk reverseDisplay! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks ! "Print this as a rough measure..." 0 tinyBenchmarks ! "Print this as a rough measure..." 0 tinyBenchmarks ! "Print this as a rough measure..." 0 tinyBenchmarks! (1 to: 10) collect: [:i | 0 tinyBenchmarks] ! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks ! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! 0 tinyBenchmarks! BlockContext allInstVarNames! 10 benchFib! 20 benchFib! 30 benchFib! 30 benchFib! 'safarieuropeletsgo!!' asSet asSortedCollection! 'safarieuropeletsgo!!' asUppercase asSet asSortedCollection! 'safarieuropeletsgo!!' asUppercase asSet asSortedCollection size! 'safarieuropeletsgo!!twitchworlds' asUppercase asSet asSortedCollection size! 'safarieuropeletsgo!!twitchworlds' asUppercase asSet asSortedCollection! 'safarieuropeletsgo!!twitchworlds' asUppercase asSet asSortedCollection! 'safarieuropeletsgo!!twitch' asUppercase asSet asSortedCollection! 'safarieuropeletsgo!!twitch' asUppercase asSet asSortedCollection size! 'safarieuropeletsgo!!worlds' asUppercase asSet asSortedCollection size! 'safarieuropeletsgo!!worldstwitch' asUppercase asSet asSortedCollection size! 'safarieuropeletsgo!!' asUppercase asSet asSortedCollection size! 'safarieuropeletsgo!!' asUppercase asSet asSortedCollection size! 'safarieuropeletsgo!!gofest' asUppercase asSet asSortedCollection size! 'safarieuropeletsgo!!worldtwitch' asUppercase asSet asSortedCollection size! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" ((JS document getElementsByTagName: 'h1') at: 0) at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38'! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! | obj | obj := JS Object new. obj at: #fib put: (JS Function new: 'n' with: 'return 2 * n'). obj fib: 20 ! JS window at: #fib put: (JS Function new: 'n' with: 'return 2 * n'). JS fib: 20 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 3 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 5 ! 5 benchFib! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 5 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 5 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 10 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 20 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 30 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 35 ! 1 benchFib! 2 benchFib! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 2 ! 2 benchFib! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 2 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2) + 1'). JS fib: 38 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2)'). JS fib: 2 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2)'). JS fib: 2 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2)'). JS fib: 3 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2)'). JS fib: 4 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2)'). JS fib: 3 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n < 2 ? n : fib(n-1) + fib(n-2)'). JS fib: 4 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n <= 1 ? n : fib(n-1) + fib(n-2)'). JS fib: 35 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n <= 1 ? n : fib(n-1) + fib(n-2)'). JS fib: 38 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n <= 1 ? n : fib(n-1) + fib(n-2)'). JS fib: 39 ! JS window at: #fib put: (JS Function new: 'n' with: 'return n <= 1 ? n : fib(n-1) + fib(n-2)'). JS fib: 38 ! | js | js := JS Object new. js at: #fib put: (JS Function new: 'n' with: 'return n <= 1 ? n : fib(n-1) + fib(n-2)'). js fib: 38 ! 5 class! SmallInteger class! 5 class class! 5 class class class! 42 class class class! Smalltalk getSystemAttribute: 0! Smalltalk getSystemAttribute: 1! Smalltalk getSystemAttribute: 2! Smalltalk getSystemAttribute: -1! Smalltalk getSystemAttribute: 1000! Smalltalk getSystemAttribute: 1001! Smalltalk getSystemAttribute: 1002! Smalltalk getSystemAttribute: 1001! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" ((JS document getElementsByTagName: 'h1') at: 0) at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'promises' stamp: 'WRB 4/26/2020 09:28'! await: promise | sem result isError | isError := false. sem := Semaphore new. promise then: [:value | result := value. sem signal]; catch: [:error | result := error. isError := true. sem signal]. sem wait. isError ifTrue: [JSException error: result]. ^result! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38'! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! JS alert: 'Squeak says Hello World!!'.! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! Time millisecondsToRun: []! Time millisecondsToRun: [25 benchmark]! Time millisecondsToRun: [30 benchmark]! Time millisecondsToRun: [50 benchmark]! Time millisecondsToRun: [100 benchmark]! Time millisecondsToRun: [100 benchmark]! Time millisecondsToRun: [1000 benchmark]! Time millisecondsToRun: [1000 benchmark]! Time millisecondsToRun: [1000 benchmark]! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" ((JS document getElementsByTagName: 'h1') at: 0) at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'promises' stamp: 'WRB 4/26/2020 09:28'! await: promise | sem result isError | isError := false. sem := Semaphore new. promise then: [:value | result := value. sem signal]; catch: [:error | result := error. isError := true. sem signal]. sem wait. isError ifTrue: [JSException error: result]. ^result! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38'! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! JS escape! JS escape: 'foo'! JS window escape: 'foo'! JS window escape! JS window escape! JS escape! JS at: #escape! JS typeof! JS Object typeof! JS typeof! argument! argument! argument == self! argument asJSArgument == argument! argument class! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" ((JS document getElementsByTagName: 'h1') at: 0) at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'promises' stamp: 'WRB 4/26/2020 09:28'! await: promise | sem result isError | isError := false. sem := Semaphore new. promise then: [:value | result := value. sem signal]; catch: [:error | result := error. isError := true. sem signal]. sem wait. isError ifTrue: [JSException error: result]. ^result! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38'! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. ! JS sqPlus! JS sqPlus! JS window sqPlus! JS await: JS window sqPlus! JS alert: 'Hi'! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! Smalltalk version! Smalltalk getSystemAttribute:1! Smalltalk getSystemAttribute:2! Smalltalk getSystemAttribute:-1! Smalltalk getSystemAttribute:-2! Smalltalk getSystemAttribute: 1004! Smalltalk getSystemAttribute: 1004! Smalltalk getSystemAttribute: 1004! "Print this as a rough measure..." 0 tinyBenchmarks! Smalltalk getSystemAttribute: 1004! Smalltalk getSystemAttribute: 1004! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks ! "Print this as a rough measure..." 0 tinyBenchmarks ! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! 22/350.0! 350.! 350.0! 350.0 / 23! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks! "Print this as a rough measure..." 0 tinyBenchmarks ! "Print this as a rough measure..." 0 tinyBenchmarks ! Object subclass: #Foo instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FooBarBaz'! Foo subclass: #Bar instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FooBarBaz'! Bar subclass: #Baz instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' category: 'FooBarBaz'! !Foo methodsFor: 'no messages' stamp: 'codefrau 3/12/2021 11:59'! something ^ #foo! ! !Bar methodsFor: 'no messages' stamp: 'codefrau 3/12/2021 11:59'! something ^{ super something. #bar}! ! !Baz methodsFor: 'no messages' stamp: 'codefrau 3/12/2021 12:00'! something ^{ super something. #baz}! ! Baz new something! Baz new halt something! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" ((JS document getElementsByTagName: 'h1') at: 0) at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'promises' stamp: 'WRB 4/26/2020 09:28'! await: promise | sem result isError | isError := false. sem := Semaphore new. promise then: [:value | result := value. sem signal]; catch: [:error | result := error. isError := true. sem signal]. sem wait. isError ifTrue: [JSException error: result]. ^result! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38'! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! !String methodsFor: '*mystuff' stamp: 'codefrau 3/16/2021 12:39'! asJQuery ^JS jQuery: self! ! | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. ! 'canvas' asJQuery hide: 'slow'; show: 'fast'.! 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}.! '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'.! JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. ! JS eval: 'sqPlus(3, 4)'.! JS eval: 'sqPlus(3, 4)'.! (JS sqPlus: 3 and: 4) then: [:result | JS alert: result].! (JS sqPlus: 3 and: 4) then: [:result | JS alert: result].! JS await: (JS sqPlus: 3 and: 4).! JS await: (JS sqPlus: 3 and: 4).! JS setTimeout: [JS alert: 'Hi'] ms: 1000.! 'canvas' asJQuery hide: 'slow'; show: 'fast'.! 'canvas' asJQuery hide: 'slow'; show: 'fast'.! 'canvas' asJQuery hide: 'slow'; show: 'fast'.! !Integer methodsFor: 'as yet unclassified' stamp: 'codefrau 3/17/2021 20:48'! bench ^5 benchFib! ! 3 bench! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" ((JS document getElementsByTagName: 'h1') at: 0) at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'promises' stamp: 'WRB 4/26/2020 09:28'! await: promise | sem result isError | isError := false. sem := Semaphore new. promise then: [:value | result := value. sem signal]; catch: [:error | result := error. isError := true. sem signal]. sem wait. isError ifTrue: [JSException error: result]. ^result! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38'! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" ((JS document getElementsByTagName: 'h1') at: 0) at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'promises' stamp: 'WRB 4/26/2020 09:28'! await: promise | sem result isError | isError := false. sem := Semaphore new. promise then: [:value | result := value. sem signal]; catch: [:error | result := error. isError := true. sem signal]. sem wait. isError ifTrue: [JSException error: result]. ^result! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38'! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11'! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12'! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 0! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" ((JS document getElementsByTagName: 'h1') at: 0) at: 'innerHTML' put: 'Squeak said Hello World at ', Time now asString. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38'! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'promises' stamp: 'WRB 4/26/2020 09:28'! await: promise | sem result isError | isError := false. sem := Semaphore new. promise then: [:value | result := value. sem signal]; catch: [:error | result := error. isError := true. sem signal]. sem wait. isError ifTrue: [JSException error: result]. ^result! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38'! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38'! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00'! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52'! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12'! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! "To reverse the display..." Smalltalk reverseDisplay ! "To reverse the display..." Smalltalk reverseDisplay ! Smalltalk version! Smalltalk getSystemAttribute: 1004! Display newDepth: 32! Display newDepth: 1! Display newDepth: 32! 100 factorial! "Print this as a rough measure..." 0 tinyBenchmarks! "To reverse the display..." Smalltalk reverseDisplay! "To reverse the display..." Smalltalk reverseDisplay! "To reverse the display..." Smalltalk reverseDisplay! "To reverse the display..." Smalltalk reverseDisplay! "Print image version..." Smalltalk version! "Print VM version..." Smalltalk getSystemAttribute: 1004! "Report issue..." JS window open: 'github.com/codefrau/SqueakJS'! JS window open: 'https://github.com/codefrau/SqueakJS'! JS window open: 'https://github.com/codefrau/SqueakJS/issues'! "Print VM version..." Smalltalk getSystemAttribute: 1004! "Print image version..." Smalltalk version! "Print VM version..." Smalltalk getSystemAttribute: 1004! JS window open: 'https://github.com/codefrau/SqueakJS/issues'! ----QUIT----(21 March 2021 12:07:31 pm ) priorSource: 292! Exception subclass: #JSException instanceVariableNames: 'jsError' classVariableNames: '' poolDictionaries: '' category: 'JSBridge-Core'! !JSException methodsFor: 'signalling' stamp: 'WRB 4/26/2020 09:11' prior: 33645104! error: error jsError := error. self signal: error message! ! "-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- "! JSException class instanceVariableNames: ''! !JSException class methodsFor: 'as yet unclassified' stamp: 'WRB 4/26/2020 09:12' prior: 33645356! error: jsError JSException new error: jsError! ! Object subclass: #JSObjectProxy instanceVariableNames: '' classVariableNames: 'CallbackSemaphore CallbackProcess' poolDictionaries: '' category: 'JSBridge-Core'! !JSObjectProxy commentStamp: '<historical>' prior: 33645627! A JSObjectProxy is a proxy for JavaScript objects. It intercepts messages to look up named properties, and call them if they are functions. Arguments are converted from Squeak to JavaScript objects for nil, Booleans, SmallIntegers, Floats, Strings, Arrays, and Dictionaries. The result is converted back to Squeak objects for numbers and null/true/false, otherwise wrapped in a new JSObjectProxy. To add new properties, or access existing properties without calling them (if they are functions), use at:/at:put:. In addition, sending #new/#new:... creates an instance of that object, and #typeof returns the type as a string. There is a global proxy named JS to allow accessing global JavaScript objects. "Call global function" JS alert: 'Squeak says Hello World!!'. "Call function on global object (open console to see result)" JS console log: 'Squeak says Hello World!!'. "Modify DOM" | h1 | h1 := JS document createElement: 'h1'. h1 innerHTML: 'Hello World!!'. h1 style: 'position: absolute; color: blue'. JS document body append: h1. "Create new Object, add properties and a method, retrieve property, call method" | obj | obj := JS Object new. obj at: #someProp put: 42. obj at: #complexProp put: {#a -> 3. #b -> 4}. obj at: #someMethod put: (JS Function new: 'return this.complexProp.a + this.complexProp.b'). {obj someProp. obj complexProp. obj someMethod} "Inspect all properties in global window object" | object propNames propValues | object := JS window. propNames := JS Object keys: object. propValues := (0 to: propNames length - 1) collect: [:i | (propNames at: i) -> (object at: (propNames at: i))]. (propValues as: Dictionary) inspect "A Squeak block becomes a JavaScript function" JS at: #sqPlus put: [:arg0 :arg1 | Transcript show: 'sqPlus called with ', arg0 asString, ' and ', arg1 asString; cr. arg0 + arg1]. "It can be called from JavaScript (open transcript to see)" JS eval: 'sqPlus(3, 4)'. "It returns a Promise. When resolved, you can access the result" JS eval: 'sqPlus(3, 4).then(function(result) { console.log(result); })'. "Which even works from Squeak ..." (JS sqPlus: 3 and: 4) then: [:result | JS alert: result]. "But instead of using JavaScript's then() function, you can use Smalltalk's semaphores!!" JS await: (JS sqPlus: 3 and: 4). "If you don't need a result, just ignore the Promise" JS setTimeout: [JS alert: 'Hi'] ms: 1000. "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38' prior: 33648716! at: aKey "get a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAt 0 0) at: 1. (error := self primGetError) ifNotNil: [^ self error: error]. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38' prior: 33648977! at: aKey put: aValue "set a property" | error | <primitive: 117> #(JavaScriptPlugin primitiveAtPut 0 0) at: 1. ^ self with: aValue retry: [:val | self at: aKey put: val] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38' prior: 33649224! doesNotUnderstand: aMessage "Call a function, or get/set an existing property. The function name / property name is the message selector up to the first colon. If the function name is 'new', create a new instance and call the constructor with args." <primitive: 117> #(JavaScriptPlugin primitiveDoUnderstand 0 0) at: 1. ^self with: aMessage arguments retry: [:args | self doesNotUnderstand: (Message selector: aMessage selector arguments: args)] ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38' prior: 33649748! printOn: aStream [aStream nextPutAll: self asString] ifError: [:err :rcvr | ^ super printOn: aStream]. ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38' prior: 33649927! asString "Convert me to a string" <primitive: 117> #(JavaScriptPlugin primitiveAsString 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38' prior: 33650126! asJSArgument ^ self ! ! !JSObjectProxy methodsFor: 'accessing' stamp: 'bf 11/20/2014 00:38' prior: 33650220! typeof "Answer my jsObject's type (a string)" <primitive: 117> #(JavaScriptPlugin primitiveTypeof 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'promises' stamp: 'WRB 4/26/2020 09:28' prior: 33650428! await: promise | sem result isError | isError := false. sem := Semaphore new. promise then: [:value | result := value. sem signal]; catch: [:error | result := error. isError := true. sem signal]. sem wait. isError ifTrue: [JSException error: result]. ^result! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38' prior: 33650783! primGetError <primitive: 117> #(JavaScriptPlugin primitiveGetError 0 0) at: 1. ^ nil ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38' prior: 33650941! primSqueakAsJSObject: anObject <primitive: 117> #(JavaScriptPlugin primitiveSqueakAsJSObject 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy methodsFor: 'private' stamp: 'bf 11/20/2014 00:38' prior: 33651142! with: argument retry: retryBlock | error | (error := self primGetError) ifNil: [^ self error: 'JSBridge error']. (error beginsWith: 'asJSArgument') ifTrue: [ ^retryBlock value: argument asJSArgument]. (error beginsWith: 'CallbackSemaphore') ifTrue: [ self class initCallbacks. ^retryBlock value: argument]. self error: error. ! ! !JSObjectProxy class methodsFor: 'instance creation' stamp: 'bf 11/20/2014 00:38' prior: 33651566! new self error: 'Use "JS Object new" to create a new JavaScript object'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00' prior: 33651730! initialize "Create the JS global" Smalltalk at: #JS put: self basicNew. "If we have the plugin, show workspace" [JS window] ifError: [:err :rcvr | ^self]. Smalltalk isMorphic ifTrue: [self openExamples] ifFalse: [[self openExamples] fork]. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00' prior: 33652069! openExamples Workspace new contents: 'Besides running regular Squeak images, SqueakJS can directly use JavaScript. It can interact with the DOM, access JavaScript libraries, and use Smalltalk code to create an interactive HTML interface. Try these examples: ', self examples; openLabel: 'JSBridge'. ! ! !JSObjectProxy class methodsFor: 'class initialization' stamp: 'bf 11/21/2014 17:00' prior: 33652464! examples | comment | "Create symbols in advance" ('sqPlus:and: alert: console log: document body append: createElement: innerHTML: style: getElementsByTagName: navigator Object keys: Function length eval: then: setTimeout:ms: createElement: appendChild: jQuery: hide: show: css: click: appendTo:' findTokens: ' ') do: [:s | s asSymbol]. comment := self organization classComment asString. ^ comment copyFrom: (comment indexOf: $") to: comment size. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52' prior: 33652953! initCallbacks CallbackProcess ifNotNil: [CallbackProcess terminate. CallbackProcess := nil]. CallbackSemaphore := Semaphore new. self primInitCallbacks: (Smalltalk registerExternalObject: CallbackSemaphore). CallbackProcess := [self callbackProcess] newProcess priority: Processor lowIOPriority; resume. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52' prior: 33653342! callbackProcess [true] whileTrue: [ CallbackSemaphore wait. [self handleCallback] fork]. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52' prior: 33653515! handleCallback | block args result | block := self primGetActiveCallbackBlock. args := self primGetActiveCallbackArgs. [result := block valueWithArguments: args] ifError: [:err :rcvr | result := JS Error: err asString]. self primReturnFromCallback: result. ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52' prior: 33653858! primInitCallbacks: semaIndex <primitive: 117> #(JavaScriptPlugin primitiveInitCallbacks 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52' prior: 33654062! primGetActiveCallbackBlock <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackBlock 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52' prior: 33654273! primGetActiveCallbackArgs <primitive: 117> #(JavaScriptPlugin primitiveGetActiveCallbackArgs 0 0) at: 1. ^ self primitiveFailed ! ! !JSObjectProxy class methodsFor: 'callbacks' stamp: 'bf 11/26/2014 18:52' prior: 33654482! primReturnFromCallback: returnValue <primitive: 117> #(JavaScriptPlugin primitiveReturnFromCallback 0 0) at: 1. ^ self primitiveFailed ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33654690! asJSArgument self error: 'Cannot convert ', self class name, ' to JavaScript'. ! ! !Object methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33654841! asJSObject "Only for debugging!! Allows to pass a Squeak object to JavaScript without converting" ^JSObjectProxy basicNew primSqueakAsJSObject: self ! ! !UndefinedObject methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33655072! asJSArgument "converted to JS null by plugin" ^self ! ! !Boolean methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33655198! asJSArgument "converted to JS true/false by plugin" ^self ! ! !SmallInteger methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33655335! asJSArgument "converted to JS number by plugin" ^self ! ! !Float methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33655461! asJSArgument "converted to JS number by plugin" ^self ! ! !Collection methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33655592! asJSArgument "converted to JS array by plugin" | array i | array := Array new: self size. i := 0. self do: [:each | array at: (i := i + 1) put: each asJSArgument]. ^ array ! ! !Dictionary methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33655845! asJSArgument "converted to JS object by plugin" | assocs i | assocs := Array new: self size. i := 0. self associationsDo: [:a | assocs at: (i := i + 1) put: a key asJSArgument -> a value asJSArgument]. ^ assocs ! ! !String methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33656135! asJSArgument "converted to JS string by plugin" self class isBytes ifTrue: [^self]. ^super asJSArgument ! ! !BlockContext methodsFor: '*jsbridge-core' stamp: 'bf 11/25/2014 18:12' prior: 33656319! asJSArgument "converted to JS function by plugin" ^self ! ! JSObjectProxy initialize! ----SNAPSHOT----(4 July 2025 11:29:39 am ) priorSource: 103028! ----SNAPSHOT----(4 July 2025 11:30:02 am ) priorSource: 115229! | h1 | h1 := JS document createElement: 'h1'. h1 innerHTML: 'Hello World!!'. h1 style: 'position: absolute; color: blue'. JS document body append: h1. ! !String methodsFor: '*mystuff' stamp: 'codefrau 7/4/2025 11:30'! asJQuery ^JS jQuery: self! ! "Now for some fun: Load jQuery, and compile a helper method" | script | (JS at: #jQuery) ifNil: [ script := JS document createElement: 'SCRIPT'. script at: 'src' put: 'https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js'. script at: 'type' put: 'text/javascript'. ((JS document getElementsByTagName: 'head') at: 0) appendChild: script. ]. String compile: 'asJQuery ^JS jQuery: self' classified: '*mystuff' notifying: nil. ! "Use jQuery" 'canvas' asJQuery hide: 'slow'; show: 'fast'. ! 'h1' asJQuery css: {'color'->'red'. 'text-shadow' -> '0 2px white, 0 3px #777'}. ! '<button>' asJQuery text: 'Hi'; click: [Transcript show: 'hi'; cr]; appendTo: 'h1'. !