rabbibotton / clog

CLOG - The Common Lisp Omnificent GUI
Other
1.49k stars 102 forks source link

Wait for script to load to run code #144

Closed mmontone closed 2 years ago

mmontone commented 2 years ago

Hello,

I'm having a problem integrating some JS I'm loading dynamically. I'm using add-script. Problem is, I need to find a way of hooking to the script's 'load' event somehow, before being able to run some initialization code, cause that init code requires the library to be already loaded.

I have something like this:

(defun init-dashboard (body)
  (load-script (html-document body) "https://gridstackjs.com/node_modules/gridstack/dist/gridstack-h5.js")
  (load-css (html-document body) "https://gridstackjs.com/node_modules/gridstack/dist/gridstack.min.css"))

(defvar *dashboard*)

(defun create-dashboard (clog-obj)
  (setf *dashboard*
        (create-div clog-obj :class "grid-stack"))
  (clog:js-execute clog-obj "window.dashboard = GridStack.init()"))

But I cannot simply call create-dashboard after init-dashboard when my program starts, as the script may not have been loaded yet, and I get JS errors.

Do you know how I can achieve this?

I thought of perhaps adding an extra on-load callback argument to add-script, but don't know how to implement, as CLOG only works with set-event, and I'm not sure I can do set-event with a script element on a load event.

Please let me know if you have any ideas.

rabbibotton commented 2 years ago

I will expose an easy way to do custom call backs, but may not be until Sunday.

mmontone commented 2 years ago

Oh. That's great. Not urgent. I'm also experimenting with a call to set-event with "load" as event.

Thanks

rabbibotton commented 2 years ago

If you want to be impatient :), a work around for now is you can have a hidden element like a div and use sets-on-change with a handler. Then your js changes the div and the event will set off on the CL side.

mmontone commented 2 years ago

Ah. Good hack. I'll see .. thanks

rabbibotton commented 2 years ago

The the set-event approach is not specific to your js but look here: https://developer.mozilla.org/en-US/docs/Web/API/Document/readystatechange_event

note in when setting the event if on window or document to use the correct clog-obj and can experiments with those for page state.

The approach I will be using is to directly fire an even from javascript through the web socket back to a handler, ie a callback

rabbibotton commented 2 years ago

I have not tried it, but it should be possible for the div to to not be in the dom for the hack - you create the div with :auto-place nil So it only is accessible via JS or CLOG no the actual page.

mmontone commented 2 years ago

I managed to make it work with this:

(defun add-script (clog-document script-url &key on-load)
  (let ((script
      (create-child (clog::head-element clog-document)
            "<script>"
            :auto-place  nil)))
    (when on-load
      (clog::set-event script "load" on-load))
    (clog::place-inside-bottom-of (clog::head-element clog-document) script)
    (setf (clog:attribute script "src") (escape-string script-url))    
    script))
rabbibotton commented 2 years ago

Great :)

rabbibotton commented 2 years ago

Actually jquery made it even easier. You just use set-on-event make up a custom event name, then in your java script $(XXX).trigger('customevent');

No need to modify CLOG.

In this example I attached the custom event to the body but could attach it to a control.

(defpackage #:cevents
  (:use #:cl #:clog)
  (:export start-app))

(in-package :cevents)

(defun custom-event (clog-obj)
  (print "Custom Event"))

(defun on-new-window (body)
  ;; Use the panel-box-layout to center horizontally
  ;; and vertically our div on the screen.
  (let* ((layout (create-panel-box-layout body))
     (hello  (create-div (center-panel layout) :content "Hello")))
    (center-children (center-panel layout))
    (set-on-click hello (lambda (obj)
              (js-execute body
                      (format nil "~A.trigger('myevent')"
                          (jquery body)))))
    (set-on-event body "myevent" 'custom-event)))

(defun start-app ()
  (initialize 'on-new-window
   :static-root (merge-pathnames "./www/"
          (asdf:system-source-directory :cevents)))
  (open-browser))
rabbibotton commented 2 years ago

I was too excited so finished up on this so that you can also pass data back to clog from js using the set-on-event-with-data method:

(defun custom-event (clog-obj data)
  (print "Custom Event")
  (print data))

(defun on-new-window (body)
  (let* ((layout (create-panel-box-layout body))
     (hello  (create-div (center-panel layout) :content "Hello")))
    (center-children (center-panel layout))
    (set-on-click hello (lambda (obj)
              (declare (ignore obj))
              (js-execute body
                      (format nil "~A.trigger('myevent', 'some data')"
                          (jquery body)))))
    (set-on-event-with-data body "myevent" 'custom-event)))
rabbibotton commented 2 years ago

Ok, add on-load-script in git

(defpackage #:cevents (:use #:cl #:clog) (:export start-app))

(in-package :cevents)

(defun custom-event (clog-obj data) (print "Custom Event") (print data))

(defun on-new-window (body) (debug-mode body) (let* ((layout (create-panel-box-layout body)) (hello (create-div (center-panel layout) :content "Hello"))) (center-children (center-panel layout)) (set-on-click hello (lambda (obj) (declare (ignore obj)) (js-execute body (format nil "~A.trigger('myevent', 'some data')" (jquery body))))) (set-on-event-with-data body "myevent" 'custom-event) (set-on-load-script (html-document body) 'custom-event) (load-script (html-document body) "/js/jquery-ui.js")))

(defun start-app () (initialize 'on-new-window :static-root (merge-pathnames "./www/" (asdf:system-source-directory :cevents))) (open-browser))

mmontone commented 2 years ago

Nice. Thanks.

rabbibotton commented 2 years ago

I have modified the default behavior when using load-script to now block return until script is loaded, This behavior is closer to html and safer overall for most apps. You can still set :wait-for-load nil and if desired you the set-on-load-script

mmontone commented 2 years ago

Nice detail. Thanks.

El mar., 14 jun. 2022 17:49, David Botton @.***> escribió:

I have modified the default behavior when using load-script to now block return until script is loaded, This behavior is closer to html and safer overall for most apps. You can still set :wait-for-load nil and if desired you the set-on-load-script

— Reply to this email directly, view it on GitHub https://github.com/rabbibotton/clog/issues/144#issuecomment-1155698675, or unsubscribe https://github.com/notifications/unsubscribe-auth/AADKPDXTVNCA4R3WQ4COACLVPDV6DANCNFSM5YN2XJTA . You are receiving this because you authored the thread.Message ID: @.***>

rabbibotton commented 2 years ago

I also added :load-only-once (default is t) to prevent multiple loads of the same js file

rabbibotton commented 2 years ago

I have also added :load-only-once (default is t) to load-css file

mmontone commented 2 years ago

I have also added :load-only-once (default is t) to load-css file

That's great to have to manage plugins dependencies. Very good.

rabbibotton commented 2 years ago

Exactly, I will be adding a template soon to the builder for new plugin also to simplify it more. To use them you just add to your asdf system depends-on :plugin-name and then have another asdf system that depends on for design time the :plug-name/tools so very simple to add plugins to your projects. I need to look in to what it takes to create our own repository for quicklisp just for plugins I think very worth while.

rabbibotton commented 2 years ago

I have improved load-script using $.getScript instead, it reliably insures not only a load but that it is ready to execute its contents.

rabbibotton commented 2 years ago

How involved do you think it would be to make a tool like your https://github.com/mmontone/lisp-system-browser using clog?

I am going to work on wrapping moving the ace-editor from demo 3 the lisp ide into a plugin and will wrap much more of its functionality. Like the builder that is an extension of slime in many ways this would be too. But give us a small-talk like system to use next to emacs, clog-builder etc. Thoughts?

mmontone commented 2 years ago

I think it would be not hard at all. If you look at lisp-system-browser, there's not a lot there.Three lists,one for packages, another for categories (functions, classes, etc) and another for the actual definitions. You already have the "heavy lifting" done by cl-def-properties, that extracts the information you need from the lisp image. And in my experience CLOG should make the UI very easy to implement. But, I'm not keen to put my time there personally, as I'm reserving my time for some dashboard application, that I'm building with CLOG. Cheers,

rabbibotton commented 2 years ago

Fantastic, I will take a look!! Looking forward to seeing your app :)

I implemented the plugin template also and extended the clog-terminal plugin so it shows custom properties and events etc