-
Notifications
You must be signed in to change notification settings - Fork 109
JSCL and manipulations with JS objects
This document describes the experience from Vladimir Mezentsev working with JSCL and JS objects. It does not reflect the any recommended practices (there is no such a thing yet).
We can use it as a starting point for a JSCL User Guide, as well as guideline to improve the FFI system.
Version: Draft
License: Copyleft
Author: mvk
Environment: Win7 / ccl v.11-64 / hunchentoot / hunchensocket
JSCL current master-branch with ffi-improvements path for compile.lisp
Chrome
(#j:window:eval "var wa = {};")
Or, so:
(setf #j:wa (cl::new))
(#j:window:eval "wa.leval = function (s) { eval(s) };")
We need such a possibility, for the call to the JS operators, such as NEW
or DELETE
, for which JSCL does not provide a direct mapping to JS.
(#j:wa:leval "this.gc = function(v) { return delete wa[v];}")
So:
(#j:wa:leval "this.property = {}")
;;=> wa.property == {}
(#j:wa:gc "property")
;;=> wa.property == undefined
;; obj.push(val)
(defun js-obj-push (obj val)
(funcall ((jscl::oget obj "push" "bind") obj val)))
;; obj.pop()
(defun js-obj-pop (obj)
(funcall ((jscl::oget obj "pop" "bind") obj )))
(setf #j:wa:temp #() temp #j:wa:temp)
(js-obj-push temp 123.2)
;;=> 1
(js-obj-push temp "aaaaa")
;;=> 2
(aref temp 0)
;;=> 123.2
temp
;;=> #(123.2 "aaaaa")
(js-obj-pop temp)
;;=> "aaaaa"
(#j:wa:gc "temp")
;;=> delete waJSCL.temp
wa.temp
;;=> undefined
temp
;;=> #(123.2 "aaaaa")
- So, if we evaluate the following expression:
(dotimes (i 10000)
(js-obj-push temp (/ i 100.1)))
(#J:wa:gc "temp")
the JS memory will be collected (gc), whereas in the JSCL - no. Would be the a direct reference on the #j:wa:temp, i.e.:
(dotimes (i 10000)
(js-obj-push #j:wa:temp (/ i 100.1)))
- Simple manipulation with JS objects.
(setf #j:wa:obj (cl::new))
(setf (cl::oget #j:wa:obj "name") "John Dow"
(cl::oget #j:wa:obj "age") "unknow")
wa.obj
;;=> Object {age: "unknow", name: "John Dow"}
wa.obj.age = 25
(cl::oget #j:wa:obj "age")
;;=> 25
- Ok. What about the manipulation of the new objects? Such as Date, for example: new Date(1234,0,1,1,1,1). JSCL current release provides only unary function new: (cl::new) => {}. So, we can use the next crutch:
(#j:wa:leval "this.ts = new Date(1234,0,1,1,1,1);")
wa
;;=> Object {ts: Sun Jan 01 1234 01:01:01 GMT+0300 (RTZ 2 (зима)), obj: Object}
wa.obj
;;=> Object {age: 25, name: "John Dow"}
(funcall ((cl::oget #j:wa:ts "getFullYear" "bind") #j:wa:ts))
;;=> 1234
(funcall ((cl::oget #j:wa:ts "setFullYear" "bind") #j:wa:ts 1999))
(funcall ((cl::oget #j:wa:ts "getFullYear" "bind") #j:wa:ts))
;;=> 1999
(setf ts #j:wa:ts)
(funcall ((cl::oget ts "setFullYear" "bind") ts 1998))
(funcall ((cl::oget ts "getFullYear" "bind") ts ))
;;=> 1998
wa.ts.getFullYear()
;;=> 1998
- Well. But the crutch of the previous paragraph, it is a crutch. So, We define a JS function such that:
/* opNew - js operator 'new' for jscl.
opNew <js-object> <sname> {args*}
<js-object> - #j:symbol (example: #j:window or #j:Konva or #j:RX)
<sname> - "name", string, (example "Date")
<args*> - arguments i.e 1234 1 0 11 23...
Pair #j:window "Date" eql window.Date (in js notation , or #j:Date in JSCL)
fn = js-object[fname] like fn=window["Date"] or fn=Konva["Stage"]
return new Function.prototype.bind.apply( fn , args)
*/
function opNew() {
var args = [].concat(null,Array.prototype.slice.call(arguments,2));
var fn = arguments[0][arguments[1]];
return new (Function.prototype.bind.apply(fn,args))();
};
(setf ts (#j:opNew #j:window "Date" 2015 11 31 23 55 0))
(funcall ((cl::oget ts "toJSON" "bind") ts))
;;=> "2015-12-31T20:55:00.000Z"
(funcall ((cl::oget ts "setTime" "bind") ts (#j:Date:now)))
(funcall ((cl::oget ts "toJSON" "bind") ts))
;;=> "2016-07-22T14:16:15.461Z"
-
Yes. I know what its dirty trick, but this worked. Actually reason for this as follows: trampoline internals.lisp_to_js(#j:window:Date) inapplicable to 'new' + Function.prototype - are lost call arguments. So, make transform #j:window:Date to pair #j:window "Date" => window["Date"] => native Date function.
-
What about new object from lisp ? Define function such that:
(defun gato (&optional (name "Murzillo") (color "black"))
(let ((self-name name)
(self-color color)
(gato-obj (cl::new))
(lg (lambda (x) (format t "~s~%" x))))
(defun log (&rest args)
(funcall lg (cl::join args " "))
(#j:console:log (cl::join args " ")))
(defun say (str)
(log "Gato:" self-name "say" str))
(defun change-color (new-color)
(let ((old-color self-color))
(log "Gato:" self-name "change color to" (setf self-color new-color) "from" old-color)))
(setf (cl::oget gato-obj "name") self-name
(cl::oget gato-obj "color") self-color
(cl::oget gato-obj "say") #'say
(cl::oget gato-obj "newColor") #'change-color
(cl::oget gato-obj) "newName" (lambda (x) (setf self-name x))
(cl::oget gato-obj) "getSelf" (lambda () cl::this)
(cl::oget gato-obj "log" ) #'log)
gato-obj))
(load "jscl/gato.lisp")
(setf #j:gato #'gato)
var cat = new gato()
;;=> Object {name: "Murzillo", color: "black"}
cat.changeColor("Red")
;;=> Gato: Murzillo change color to Red from black
cat.name = "Vasjka"
cat
;;=> Object {name: "Vasjka", color: "black"}
cat.say("Hello")
;;=> Gato: Murzillo say hello
WTF? Yes, we have forgotten about self-name setter. New redaction for gato:
(defun gato (&optional (name "Murzillo") (color "black"))
(let ((self-name name)
(self-color color)
(gato-obj (cl::new))
(lg (lambda (x) (format t "~s~%" x))))
(defun log (&rest args)
(funcall lg (cl::join args " "))
(#j:console:log (cl::join args " ")))
(defun say (str)
(log "Gato:" self-name "say" str))
(defun change-color (new-color)
(let ((old-color self-color))
(log "Gato:" self-name "change color to" (setf self-color new-color) "from" old-color)))
(setf (cl::oget gato-obj "name")
(lambda (&optional x)
(if (null x) self-name
(let ((old self-name)) (setf self-name x) old)))
(cl::oget gato-obj "color") (lambda () self-color)
(cl::oget gato-obj "say") #'say
(cl::oget gato-obj "newColor") #'change-color
(cl::oget gato-obj "newName") (lambda (x) (setf self-name x))
(cl::oget gato-obj "getSelf") (lambda () cl::this)
(cl::oget gato-obj "log" ) #'log)
gato-obj))
Now:
(load "jscl/gato.lisp")
(setf #j:gato #'gato)
var cat = new gato()
;;=> Object {}
cat.name()
;;=> "Murzillo"
cat.name("Garfield")
;;=> "Murzillo"
cat.name()
;;=> "Garfield"
cat.say("Hello")
;;=> Gato: Garfield say hello
- Other trick
Define global lisp variable date, and function set-date, such that:
(defparameter *date* nil)
(defun set-date (ts)
(if *date*
(funcall ((cl::oget *date* "setTime" "bind") *date* ts))
nil))
Also, added to js object "cat" next propertie:
(setf (cl::oget #j:cat "initDate") (lambda (x) (setf *date* x))
(cl::oget #j:cat "setDate") (lambda (x) (set-date x)))
cat.initDate(new Date(1234,0,1,1,1,1))
;;=> Sun Jan 01 1234 01:01:01 GMT+0300 (RTZ 2 (зима))
(#j:cat:say *date*)
;;=> "Gato: Garfield say Sun Jan 01 1234 01:01:01 GMT+0300 (RTZ 2 (зима))"
(#j:cat:setDate (#J:Date:now))
;;=> 1469369844507
(#j:cat:say *date*)
;;=> "Gato: Garfield say Sun Jul 24 2016 17:17:24 GMT+0300 (RTZ 2 (зима))"
(setf cat-garfield (#j:cat:getSelf))
(funcall ((cl::oget cat-garfield "say" "bind") cat-garfield (cl::concat "Now " *date*)))
;;=> "Gato: Garfield say Now Sun Jul 24 2016 17:17:24 GMT+0300 (RTZ 2 (зима))"