Skip to content

JSCL and manipulations with JS objects

peccu edited this page Dec 4, 2016 · 4 revisions

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

1. Create js object with name "wa". It is possible to make so:

   (#j:window:eval "var wa = {};")

Or, so:

   (setf #j:wa (cl::new))

2. Define local eval such that:

   (#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

3. Simple manipulation with js area, look as:

   ;; 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"

4. Notice the following:

   (#j:wa:gc "temp")
   ;;=> delete waJSCL.temp
   
   wa.temp
   ;;=> undefined
   
   temp
   ;;=> #(123.2 "aaaaa")
  1. 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)))
  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
  1. 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
  1. 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"
  1. 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.

  2. 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
  1. 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 (зима))"