Compare commits
6 Commits
cc26bd7e13
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 231d30c561 | |||
| 325e890737 | |||
| c1b0d8559c | |||
| dff979350a | |||
| 18cd4d3bd9 | |||
| 3cb7f56167 |
@@ -1,14 +1,14 @@
|
||||
#lang info
|
||||
|
||||
(define pkg-authors '(hnmdijkema))
|
||||
(define version "0.1.6")
|
||||
(define version "0.1.7")
|
||||
(define license 'MIT)
|
||||
(define collection "racket-webview")
|
||||
(define pkg-desc "racket-webview - A Web Based GUI library, based on a Qt WebEngine backend")
|
||||
|
||||
(define scribblings
|
||||
'(
|
||||
|
||||
("scrbl/racket-webview-collection.scrbl" (multi-page) (gui-library 0) "racket-webview")
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -281,6 +281,9 @@
|
||||
(syntax-case stx ()
|
||||
((_ (a c b1 b2))
|
||||
(cond
|
||||
((eq? (syntax->datum #'a) 'define) #'(js-def c (b1 b2)))
|
||||
((eq? (syntax->datum #'a) 'lambda) #'(js-lambda c b1 b2))
|
||||
((eq? (syntax->datum #'a) 'λ) #'(js-lambda c b1 b2))
|
||||
((eq? (syntax->datum #'a) 'if) #'(js2-if c b1 b2))
|
||||
((memq (syntax->datum #'a) js-ops) #'(js-op a c b1 b2))
|
||||
((eq? (syntax->datum #'a) 'let*) #'(js2-let* c b1 b2))
|
||||
@@ -321,6 +324,7 @@
|
||||
((eq? (syntax->datum #'a) 'return) #'(js-return b))
|
||||
((eq? (syntax->datum #'a) 'quote) #'(js-quote b))
|
||||
((eq? (syntax->datum #'a) 'eval) #'(js-eval b))
|
||||
((eq? (syntax->datum #'a) 'list) #'(js-op a b))
|
||||
;string-append
|
||||
; "\"" (esc-double-quote (format "~a" 'b)) "\""))
|
||||
(else
|
||||
@@ -334,6 +338,8 @@
|
||||
(cond
|
||||
((eq? (syntax->datum #'a) 'let*) #'(js2-let* c b1 ...))
|
||||
((eq? (syntax->datum #'a) 'define) #'(js-def c (b1 ...)))
|
||||
((eq? (syntax->datum #'a) 'lambda) #'(js-lambda c b1 ...))
|
||||
((eq? (syntax->datum #'a) 'λ) #'(js-lambda c b1 ...))
|
||||
((memq (syntax->datum #'a) js-ops) #'(js-op a c b1 ...))
|
||||
((eq? (syntax->datum #'a) 'begin) #'(js-begin c b1 ...))
|
||||
((eq? (syntax->datum #'a) 'let) #'(error "let is not supported in js context, use let*"))
|
||||
@@ -392,10 +398,11 @@
|
||||
#|
|
||||
(define t1
|
||||
(js (set! window.myfunc (λ (x)
|
||||
(let* ((el (document.getElementById 'hi))
|
||||
(let* ((el (send document getElementById 'hi))
|
||||
(y (* x x)))
|
||||
(el.setAttribute "x" (+ y ""))
|
||||
(send el setAttribute "x" (+ y ""))
|
||||
)
|
||||
(send console log "dit set attribute x on element hi")
|
||||
)
|
||||
)))
|
||||
|
||||
@@ -408,6 +415,33 @@
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define t3 (js (define (f x y z)
|
||||
(send console log (cons x (cons y (list z))))
|
||||
(let* ((l (cons x (cons y (list z)))))
|
||||
(return (send l map (λ (a) (return (+ a 10)))))
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
==>
|
||||
function f(x, y, z) {
|
||||
console.log([ x].concat([ y].concat([ z])));
|
||||
{
|
||||
let l = [ x].concat([ y].concat([ z]));
|
||||
return (l.map(function (a) { return (a + 10);
|
||||
;
|
||||
}
|
||||
));
|
||||
;
|
||||
|
||||
}
|
||||
;
|
||||
|
||||
}
|
||||
;
|
||||
|
||||
|#
|
||||
|
||||
|
||||
|
||||
+39
-25
@@ -30,6 +30,7 @@
|
||||
lru-cache
|
||||
racket-self-signed-cert
|
||||
simple-log
|
||||
js-maker
|
||||
)
|
||||
|
||||
(provide webview-new-context
|
||||
@@ -396,6 +397,12 @@
|
||||
#f))
|
||||
|
||||
|
||||
(define-syntax with-id->el
|
||||
(syntax-rules ()
|
||||
((_ id el e1 ...)
|
||||
(js (let ((el (send document getElementById (eval id))))
|
||||
e1 ...)))))
|
||||
|
||||
(define-syntax with-id
|
||||
(syntax-rules ()
|
||||
((_ id el
|
||||
@@ -718,10 +725,11 @@
|
||||
(wv-win-window-nr wv) sel evt (if no-prevent-default 'true 'false)))
|
||||
(r (webview-call-js wv j))
|
||||
)
|
||||
;(dbg-webview "called js: ~a" j)
|
||||
(map (λ (el)
|
||||
(list (string->symbol (car el)) (cadr el) (caddr el)))
|
||||
r))))
|
||||
(if (eq? r #f)
|
||||
#f
|
||||
(map (λ (el)
|
||||
(list (string->symbol (car el)) (cadr el) (caddr el)))
|
||||
r)))))
|
||||
|
||||
(define/contract (webview-unbind! wv selector event)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c symbol? list-of-symbol?) list?)
|
||||
@@ -735,9 +743,11 @@
|
||||
(let ((r (webview-call-js wv
|
||||
(format "return window.rkt_unbind_evt_ids(~a, '~a', ~a)"
|
||||
(wv-win-window-nr wv) sel evt))))
|
||||
(map (λ (el)
|
||||
(list (string->symbol (car el)) (cadr el) (caddr el)))
|
||||
r))))
|
||||
(if (eq? r #f)
|
||||
#f
|
||||
(map (λ (el)
|
||||
(list (string->symbol (car el)) (cadr el) (caddr el)))
|
||||
r)))))
|
||||
|
||||
(define/contract (webview-run-js wv js)
|
||||
(-> wv-win? string? symbol?)
|
||||
@@ -762,15 +772,14 @@
|
||||
|
||||
(define/contract (webview-call-js wv js)
|
||||
(-> wv-win? string? (or/c string? list? boolean? hash? symbol? number?))
|
||||
(displayln js)
|
||||
(let ((result (rkt-webview-call-js (wv-win-handle wv) js)))
|
||||
;(displayln result)
|
||||
(if (webview-call-js-result? result)
|
||||
(if (eq? (car result) 'oke)
|
||||
(hash-ref (fromJson (cadr result)) 'result #f)
|
||||
(error
|
||||
(format "Error calling javascript. Message: ~a"
|
||||
(hash-ref (fromJson (cadr result)) 'exn result)))
|
||||
)
|
||||
(begin
|
||||
(err-webview "Error calling javascript: ~a" result)
|
||||
#f))
|
||||
(error
|
||||
(format "Wrong result from webview-call-js: ~a" result))
|
||||
)
|
||||
@@ -785,9 +794,12 @@
|
||||
(-> wv-win? symbol? (or/c string? xexpr?) symbol?)
|
||||
(if (string? html)
|
||||
(let ((r (webview-call-js wv
|
||||
(with-id id el
|
||||
("el.innerHTML = '~a'; return true;"
|
||||
(esc-quote html))))))
|
||||
(with-id->el id el
|
||||
(set! (js-dot el innerHTML) (eval html))
|
||||
#t))))
|
||||
;(with-id id el
|
||||
; ("el.innerHTML = '~a'; return true;"
|
||||
; (esc-quote html))))))
|
||||
(if r 'oke 'failed))
|
||||
(webview-set-innerHTML! wv id (xexpr->string html))
|
||||
)
|
||||
@@ -859,7 +871,7 @@
|
||||
|
||||
|
||||
(define/contract (webview-add-class! wv id-or-selector class)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c symbol? string? list?) hash?)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c symbol? string? list?) (or/c hash? boolean?))
|
||||
(let ((sel (if (symbol? id-or-selector)
|
||||
(format "#~a" id-or-selector)
|
||||
id-or-selector))
|
||||
@@ -879,7 +891,7 @@
|
||||
)
|
||||
|
||||
(define/contract (webview-remove-class! wv id-or-selector class)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c symbol? string? list?) hash?)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c symbol? string? list?) (or/c hash? boolean?))
|
||||
(let ((sel (if (symbol? id-or-selector)
|
||||
(format "#~a" id-or-selector)
|
||||
id-or-selector))
|
||||
@@ -901,7 +913,7 @@
|
||||
)
|
||||
|
||||
(define/contract (webview-set-style! wv selector style-entries)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c kv? list-of-kv?) hash?)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c kv? list-of-kv?) (or/c hash? boolean?))
|
||||
(let ((sel (if (symbol? selector)
|
||||
(format "#~a" selector)
|
||||
selector))
|
||||
@@ -947,18 +959,20 @@
|
||||
" return { id: id, style: r };"
|
||||
"}") cl))
|
||||
)))
|
||||
(let ((h (hash-ref r 'with-ids)))
|
||||
(let ((l (map (λ (e) (cons (string->symbol (hash-ref e 'id)) (hash-ref e 'style))) h)))
|
||||
(if (symbol? selector)
|
||||
(cdr (car l))
|
||||
l)))
|
||||
(if (eq? r #f)
|
||||
#f
|
||||
(let ((h (hash-ref r 'with-ids)))
|
||||
(let ((l (map (λ (e) (cons (string->symbol (hash-ref e 'id)) (hash-ref e 'style))) h)))
|
||||
(if (symbol? selector)
|
||||
(cdr (car l))
|
||||
l))))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
(define/contract (webview-unset-style! wv selector style-entries)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c symbol? list-of-symbol?) hash?)
|
||||
(-> wv-win? (or/c symbol? string?) (or/c symbol? list-of-symbol?) (or/c hash? boolean?))
|
||||
(let ((sel (if (symbol? selector)
|
||||
(format "#~a" selector)
|
||||
selector))
|
||||
@@ -984,7 +998,7 @@
|
||||
|
||||
(define/contract (webview-set-attr! wv selector attr-entries)
|
||||
(-> wv-win? (or/c symbol? string?)
|
||||
(or/c kv? list-of-kv?) hash?)
|
||||
(or/c kv? list-of-kv?) (or/c hash? boolean?))
|
||||
(let* ((sel (if (symbol? selector)
|
||||
(format "#~a" selector)
|
||||
selector))
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
#lang scribble/manual
|
||||
|
||||
@(require (for-label racket/base
|
||||
"../private/js-transform.rkt"))
|
||||
|
||||
@title{JavaScript Transformation}
|
||||
@author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]]
|
||||
|
||||
@defmodule[racket-webview/private/js-transform]
|
||||
|
||||
This module provides a small compile-time DSL for generating JavaScript source
|
||||
text from Racket-like forms. It is used internally by the webview
|
||||
implementation to construct JavaScript snippets in a structured way, without
|
||||
having to write the complete snippet as one raw string.
|
||||
|
||||
The module exports only @racket[js]. The other forms described in this section
|
||||
are DSL forms recognized by @racket[js]; they are not exported as separate
|
||||
bindings.
|
||||
|
||||
@defform[
|
||||
(js js-statement ...)
|
||||
]{
|
||||
|
||||
Transforms each @racket[js-statement] into JavaScript source text. The result
|
||||
is a string. The generated string is not evaluated by Racket and no JavaScript
|
||||
is executed by this module.
|
||||
|
||||
Each top-level statement is emitted with a trailing semicolon and newline. The
|
||||
resulting string can be passed to the webview layer for injection or execution
|
||||
in the browser context.
|
||||
|
||||
For example:
|
||||
|
||||
@racketblock[
|
||||
(define source
|
||||
(js
|
||||
(set! window.myfunc
|
||||
(λ (x)
|
||||
(let* ((el (send document getElementById 'hi))
|
||||
(y (* x x)))
|
||||
(send el setAttribute "x" (+ y "")))
|
||||
(send console log "did set attribute x on element hi")))))
|
||||
]
|
||||
|
||||
This generates JavaScript source that assigns a function to
|
||||
@tt{window.myfunc}. The function looks up a DOM element, calculates a value,
|
||||
sets an attribute, and writes a message to the JavaScript console.
|
||||
}
|
||||
|
||||
@section{Primitive values}
|
||||
|
||||
Numbers are emitted as JavaScript numeric literals. Strings are emitted as
|
||||
double-quoted JavaScript strings, with embedded double quotes escaped.
|
||||
Identifiers are emitted as JavaScript names.
|
||||
|
||||
For example:
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(send console log "hello")
|
||||
(send console log 42))
|
||||
]
|
||||
|
||||
Symbols can be quoted to produce JavaScript string values:
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(send console log 'hello))
|
||||
]
|
||||
|
||||
@section{Function calls and method calls}
|
||||
|
||||
A form that is not recognized as a special DSL form is treated as a JavaScript
|
||||
function call:
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(alert "Hello from Racket"))
|
||||
]
|
||||
|
||||
This generates a JavaScript call to @tt{alert}.
|
||||
|
||||
Method calls can be written with @racket[send]:
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(send document getElementById 'hi))
|
||||
]
|
||||
|
||||
The @racket[send] form has the shape:
|
||||
|
||||
@racketblock[
|
||||
(send object method arg ...)
|
||||
]
|
||||
|
||||
and generates a JavaScript method call of the form:
|
||||
|
||||
@codeblock|{
|
||||
object.method(arg, ...)
|
||||
}|
|
||||
|
||||
For example, @racket[(send document getElementById 'hi)] generates a call shaped
|
||||
like:
|
||||
|
||||
@codeblock|{
|
||||
document.getElementById("hi")
|
||||
}|
|
||||
|
||||
Using @racket[send] keeps the method-call structure explicit in the DSL. It is
|
||||
usually clearer than writing dotted JavaScript names directly as function names.
|
||||
|
||||
@section{Operators}
|
||||
|
||||
The DSL supports a small set of JavaScript infix operators.
|
||||
|
||||
@itemlist[
|
||||
#:style 'compact
|
||||
|
||||
@item{@racket[(+ a b ...)], @racket[(- a b ...)],
|
||||
@racket[(* a b ...)] and @racket[(/ a b ...)] generate arithmetic infix
|
||||
expressions.}
|
||||
|
||||
@item{@racket[(and a b ...)] and @racket[(or a b ...)] generate JavaScript
|
||||
@tt{&&} and @tt{||} expressions.}
|
||||
|
||||
@item{@racket[(> a b)], @racket[(< a b)], @racket[(>= a b)],
|
||||
@racket[(<= a b)], @racket[(== a b)], @racket[(=== a b)] and
|
||||
@racket[(!= a b)] generate JavaScript comparison expressions.}
|
||||
]
|
||||
|
||||
For example:
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(define (inside-range x)
|
||||
(if (and (> x 10) (< x 15))
|
||||
(return x)
|
||||
(return (* x x)))))
|
||||
]
|
||||
|
||||
@section{Definitions and assignments}
|
||||
|
||||
@defform[(define (name arg ...) body ...)]{
|
||||
|
||||
Generates a JavaScript function declaration.
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(define (square x)
|
||||
(return (* x x))))
|
||||
]
|
||||
|
||||
This produces a JavaScript function named @tt{square}.
|
||||
}
|
||||
|
||||
@defform[(set! name expr)]{
|
||||
|
||||
Generates a JavaScript assignment.
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(set! window.answer 42))
|
||||
]
|
||||
|
||||
This produces an assignment to @tt{window.answer}.
|
||||
}
|
||||
|
||||
@section{Functions}
|
||||
|
||||
@defform[(lambda (arg ...) body ...)]{
|
||||
Generates a JavaScript function expression.
|
||||
}
|
||||
|
||||
@defform[(λ (arg ...) body ...)]{
|
||||
Equivalent to @racket[lambda].
|
||||
}
|
||||
|
||||
For example:
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(set! window.square
|
||||
(λ (x)
|
||||
(return (* x x)))))
|
||||
]
|
||||
|
||||
A function body may contain more than one DSL statement:
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(set! window.f
|
||||
(λ (x)
|
||||
(send console log x)
|
||||
(return (* x x)))))
|
||||
]
|
||||
|
||||
@section{Control flow and statement blocks}
|
||||
|
||||
@defform[(if condition then-expr else-expr)]{
|
||||
|
||||
Generates a JavaScript @tt{if}/@tt{else} statement. The condition is translated
|
||||
as a JavaScript expression. The two branches are translated as JavaScript
|
||||
statements.
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(define (f x)
|
||||
(if (> x 0)
|
||||
(return x)
|
||||
(return (- 0 x)))))
|
||||
]
|
||||
}
|
||||
|
||||
@defform[(begin body ...)]{
|
||||
|
||||
Generates a JavaScript block.
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(if (> x 10)
|
||||
(begin
|
||||
(send console log x)
|
||||
(return x))
|
||||
(return 0)))
|
||||
]
|
||||
}
|
||||
|
||||
@defform[(return expr)]{
|
||||
|
||||
Generates a JavaScript @tt{return} statement.
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(define (id x)
|
||||
(return x)))
|
||||
]
|
||||
}
|
||||
|
||||
@section{Sequential bindings}
|
||||
|
||||
@defform[(let* ((id expr) ...) body ...)]{
|
||||
|
||||
Generates a JavaScript block with sequential @tt{let} declarations, followed by
|
||||
the generated body statements.
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(let* ((x 10)
|
||||
(y (* x x)))
|
||||
(send console log y)
|
||||
(return y)))
|
||||
]
|
||||
|
||||
The plain @racket[let] form is intentionally not supported in a JavaScript
|
||||
context. Use @racket[let*] instead, so that the generated JavaScript bindings
|
||||
remain explicitly sequential.
|
||||
}
|
||||
|
||||
@section{Lists}
|
||||
|
||||
@defform[(list expr ...)]{
|
||||
|
||||
Generates a JavaScript array literal.
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(send console log (list 1 2 3)))
|
||||
]
|
||||
}
|
||||
|
||||
@defform[(cons expr list-expr)]{
|
||||
|
||||
Generates JavaScript that prepends a value to an array-like expression by
|
||||
concatenating a one-element array with the translated list expression.
|
||||
|
||||
For example:
|
||||
|
||||
@racketblock[
|
||||
(js
|
||||
(send console log (cons x (cons y (list z)))))
|
||||
]
|
||||
|
||||
This generates JavaScript shaped like:
|
||||
|
||||
@codeblock|{
|
||||
console.log([ x].concat([ y].concat([ z])));
|
||||
}|
|
||||
}
|
||||
|
||||
@section{Embedding Racket values}
|
||||
|
||||
@defform[(eval value)]{
|
||||
|
||||
Embeds a simple Racket value as a JavaScript literal at expansion time. The
|
||||
supported values are strings, symbols, numbers and lists containing those same
|
||||
kinds of values.
|
||||
|
||||
@racketblock[
|
||||
(define names '(alice bob charlie))
|
||||
|
||||
(js
|
||||
(send console log (eval names)))
|
||||
]
|
||||
|
||||
The value is converted to JavaScript source text before the generated JavaScript
|
||||
is returned.
|
||||
}
|
||||
|
||||
@section{Example}
|
||||
|
||||
The following example combines function definition, method calls, list
|
||||
construction, sequential bindings and a lambda expression passed to a JavaScript
|
||||
method:
|
||||
|
||||
@racketblock[
|
||||
(define source
|
||||
(js
|
||||
(define (f x y z)
|
||||
(send console log (cons x (cons y (list z))))
|
||||
(let* ((l (cons x (cons y (list z)))))
|
||||
(return
|
||||
(send l map
|
||||
(λ (a)
|
||||
(return (+ a 10)))))))))
|
||||
]
|
||||
|
||||
This generates a JavaScript function that logs an array, constructs the same
|
||||
array in a local binding, maps over it, and returns the mapped result.
|
||||
|
||||
@section{Limitations}
|
||||
|
||||
This transformer is intentionally small. It is not a complete JavaScript
|
||||
parser, not a JavaScript evaluator and not a general Racket-to-JavaScript
|
||||
compiler. Unrecognized forms are generally treated as JavaScript function
|
||||
calls. This keeps the implementation compact, but it also means that some
|
||||
mistakes may produce JavaScript source that only fails when the browser runs the
|
||||
generated code.
|
||||
|
||||
The generated source is intended for small snippets used by the webview layer,
|
||||
not for translating arbitrary Racket programs to JavaScript.
|
||||
@@ -17,6 +17,7 @@
|
||||
@include-section["wv-dialog.scrbl"]
|
||||
@include-section["wv-element.scrbl"]
|
||||
@include-section["wv-input.scrbl"]
|
||||
@include-section["js-transform.scrbl"]
|
||||
@include-section["racket-webview.scrbl"]
|
||||
@include-section["racket-webview-qt.scrbl"]
|
||||
@include-section["rgba.scrbl"]
|
||||
|
||||
Reference in New Issue
Block a user