clog/clog.lisp
2020-12-22 22:00:37 -05:00

1002 lines
31 KiB
Common Lisp

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; CLOG - The Common Lisp Omnificent GUI ;;;;
;;;; (c) 2020-2021 David Botton ;;;;
;;;; License BSD 3 Clause ;;;;
;;;; ;;;;
;;;; clog.lisp ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exports - clog
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mgl-pax:define-package :clog
(:documentation "The Common List Omnificent GUI - Parent package")
(:local-nicknames (:cc :clog-connection))
(:use #:cl #:mgl-pax))
(in-package :clog)
(defsection @clog-manual (:title "The CLOG manual")
"The Common Lisp Omnificient GUI, CLOG for short, uses web technology
to produce graphical user interfaces for applications locally or
remotely. The CLOG package starts up the connectivity to the browser
or other websocket client (often a browser embedded in a native
application."
(clog asdf:system)
(@clog-system section)
(@clog-objs section))
(defsection @clog-system (:title "CLOG System")
"CLOG Startup and Shutdown"
(initialize function)
(shutdown function)
"CLOG Low Level binding functions"
(attach function)
(create-with-html function)
"CLOG utilities"
(js-true-p function)
(open-browser function))
(defsection @clog-objs (:title "CLOG Objects")
"CLOG-Obj"
(clog-obj class)
"CLOG-Obj - General Properties"
(property generic-function)
(style generic-function)
(attribute generic-function)
(height generic-function)
(width generic-function)
"CLOG-Obj - General Methods"
(focus generic-function)
(blur generic-function)
"CLOG-Obj - Placement"
(place-after generic-function)
(place-before generic-function)
(place-inside-top-of generic-function)
(place-inside-bottom-of generic-function)
"CLOG-Obj - Low Level"
(create-child generic-function)
(attach-as-child generic-function)
(connection-data generic-function)
(validp generic-function)
"CLOG-Obj - Event Handling"
(set-on-focus generic-function)
(set-on-blur generic-function)
(set-on-change generic-function)
(set-on-focus-in generic-function)
(set-on-focus-out generic-function)
(set-on-reset generic-function)
(set-on-search generic-function)
(set-on-select generic-function)
(set-on-submit generic-function)
(set-on-select generic-function)
(set-on-context-menu generic-function)
(set-on-click generic-function)
(set-on-double-click generic-function)
(set-on-mouse-click generic-function)
(set-on-mouse-double-click generic-function)
(set-on-mouse-right-click generic-function)
(set-on-mouse-enter generic-function)
(set-on-mouse-leave generic-function)
(set-on-mouse-over generic-function)
(set-on-mouse-out generic-function)
(set-on-mouse-down generic-function)
(set-on-mouse-up generic-function)
(set-on-mouse-move generic-function)
(set-on-character generic-function)
(set-on-key-down generic-function)
(set-on-key-up generic-function)
(set-on-key-press generic-function)
(set-on-copy generic-function)
(set-on-cut generic-function)
(set-on-paste generic-function))
;; need to add drag and drop events
;; lookup new touch events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Implementation - clog-obj
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defclass clog-obj ()
((connection-id
:reader connection-id
:initarg :connection-id)
(html-id
:reader html-id
:initarg :html-id)
(event-handlers
:accessor event-handlers
:initform (make-hash-table :test #'equal)))
(:documentation "CLOG objects (clog-obj) encapsulate the connection between
lisp and the HTML DOM element."))
(defgeneric connection-id (clog-obj)
(:documentation "Reader for connection-id slot. (Private)"))
(defgeneric html-id (clog-obj)
(:documentation "Reader for html-id slot. (Private)"))
(defgeneric event-handlers (clog-obj)
(:documentation "Reader/writer for even-handler hash. (Private)"))
;;;;;;;;;;;;;;;;;;;
;; make-clog-obj ;;
;;;;;;;;;;;;;;;;;;;
(defun make-clog-obj (connection-id html-id)
"Construct a new clog-obj. (Private)"
(make-instance 'clog-obj :connection-id connection-id
:html-id html-id))
;;;;;;;;;;;;;;;
;; script-id ;;
;;;;;;;;;;;;;;;
(defgeneric script-id (clog-obj)
(:documentation "Return the script id for OBJ based on the html-id set
during attachment. (Private)"))
(defmethod script-id ((obj clog-obj))
(if (eql (html-id obj) 0)
"'body'"
(format nil "clog['~A']" (html-id obj))))
;;;;;;;;;;;;
;; jquery ;;
;;;;;;;;;;;;
(defgeneric jquery (clog-obj)
(:documentation "Return the jquery accessor for OBJ. (Private)"))
(defmethod jquery ((obj clog-obj))
(format nil "$(~A)" (script-id obj)))
;;;;;;;;;;;;;;;;;;;;
;; jquery-execute ;;
;;;;;;;;;;;;;;;;;;;;
(defgeneric jquery-execute (clog-obj method)
(:documentation "Execute the jquery METHOD on OBJ. Result is
dicarded. (Private)"))
(defmethod jquery-execute ((obj clog-obj) method)
(cc:execute (connection-id obj)
(format nil "~A.~A" (jquery obj) method)))
;;;;;;;;;;;;;;;;;;
;; jquery-query ;;
;;;;;;;;;;;;;;;;;;
(defgeneric jquery-query (clog-obj method)
(:documentation "Execute the jquery METHOD on OBJ and return
result. (Private)"))
(defmethod jquery-query ((obj clog-obj) method)
(cc:query (connection-id obj)
(format nil "~A.~A" (jquery obj) method)))
;;;;;;;;;;;;;;;;;;;;;;;
;; bind-event-script ;;
;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric bind-event-script (clog-obj event call-back &key cancel-event)
(:documentation "Create the code client side for EVENT CALL-BACK. (Private)"))
(defmethod bind-event-script ((obj clog-obj) event call-back
&key (cancel-event nil))
(if cancel-event
(jquery-execute
obj (format nil "on('~A',function (e, data){~A});return false"
event call-back))
(jquery-execute
obj (format nil "on('~A',function (e, data){~A})" event call-back))))
;;;;;;;;;;;;;;;;;;;;;;;;;
;; unbind-event-script ;;
;;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric unbind-event-script (clog-obj event)
(:documentation "Remove the client call back code for EVENT. (Private)"))
(defmethod unbind-event-script ((obj clog-obj) event)
(jquery-execute obj (format nil "off(~A)" event)))
;;;;;;;;;;;;;;;;;;;;;;;
;; parse-mouse-event ;;
;;;;;;;;;;;;;;;;;;;;;;;
(defconstant mouse-event-script
"+ (e.clientX - e.target.getBoundingClientRect().left) + ':' +
(e.clientY - e.target.getBoundingClientRect().top) + ':' +
e.screenX + ':' + e.screenY + ':' + e.which + ':' + e.altKey + ':' +
e.ctrlKey + ':' + e.shiftKey + ':' + e.metaKey")
;; e.buttons would be better but not supported currently outside
;; of firefox and would always return 0 on Mac so using e.which.
;; The use of offsetLeft and offsetTop is to correct the X and Y
;; to the actual X,Y of the target.
(defun parse-mouse-event (data)
(let ((f (ppcre:split ":" data)))
(list
:x (parse-integer (nth 0 f) :junk-allowed t)
:y (parse-integer (nth 1 f) :junk-allowed t)
:screen-y (parse-integer (nth 2 f) :junk-allowed t)
:screen-x (parse-integer (nth 3 f) :junk-allowed t)
:which-button (parse-integer (nth 4 f) :junk-allowed t)
:alt-key (js-true-p (nth 5 f))
:ctrl-key (js-true-p (nth 6 f))
:shift-key (js-true-p (nth 7 f))
:meta-key (js-true-p (nth 8 f)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;
;; parse-keyboard-event ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;
(defconstant keyboard-event-script
"+ e.keyCode + ':' + e.charCode + ':' + e.altKey + ':' + e.ctrlKey + ':'
+ e.shiftKey + ':' + e.metaKey")
(defun parse-keyboard-event (data)
(let ((f (ppcre:split ":" data)))
(list
:key-code (parse-integer (nth 0 f) :junk-allowed t)
:char-code (parse-integer (nth 1 f) :junk-allowed t)
:alt-key (js-true-p (nth 2 f))
:ctrl-key (js-true-p (nth 3 f))
:shift-key (js-true-p (nth 4 f))
:meta-key (js-true-p (nth 5 f)))))
;;;;;;;;;;;;;;;
;; set-event ;;
;;;;;;;;;;;;;;;
(defgeneric set-event (clog-obj event handler &key call-back-script)
(:documentation "Create the hood for incoming events. (Private)"))
(defmethod set-event ((obj clog-obj) event handler &key (call-back-script ""))
;; meeds mutex
(let ((hook (format nil "~A:~A" (html-id obj) event)))
(cond (handler
(bind-event-script
obj event (format nil "ws.send('E:~A-'~A)" hook call-back-script))
(setf (gethash hook (connection-data obj)) handler))
(t
(unbind-event-script obj event)
(remhash hook (connection-data obj))))))
;;;;;;;;;;;;;;
;; property ;;
;;;;;;;;;;;;;;
(defgeneric property (clog-obj property-name)
(:documentation "Get/Setf html property. (eg. draggable)"))
(defmethod property ((obj clog-obj) property-name)
(jquery-query obj (format nil "prop('~A')" property-name)))
(defgeneric set-property (clog-obj property-name value)
(:documentation "Set html property."))
(defmethod set-property ((obj clog-obj) property-name value)
(jquery-execute obj (format nil "prop('~A','~A')" property-name value)))
(defsetf property set-property)
;;;;;;;;;;;
;; style ;;
;;;;;;;;;;;
(defgeneric style (clog-obj style-name)
(:documentation "Get/Setf css style."))
(defmethod style ((obj clog-obj) style-name)
(jquery-query obj (format nil "css('~A')" style-name)))
(defgeneric set-style (clog-obj style-name value)
(:documentation "Set css style."))
(defmethod set-style ((obj clog-obj) style-name value)
(jquery-execute obj (format nil "css('~A','~A')" style-name value)))
(defsetf style set-style)
;;;;;;;;;;;;;;;
;; attribute ;;
;;;;;;;;;;;;;;;
(defgeneric attribute (clog-obj attribute-name)
(:documentation "Get/Setf html tag attribute. (eg. src on img tag)"))
(defmethod attribute ((obj clog-obj) attribute-name)
(jquery-query obj (format nil "attr('~A')" attribute-name)))
(defgeneric set-attribute (clog-obj attribute-name value)
(:documentation "Set html tag attribute."))
(defmethod set-attribute ((obj clog-obj) attribute-name value)
(jquery-execute obj (format nil "attr('~A','~A')" attribute-name value)))
(defsetf attribute set-attribute)
;;;;;;;;;;;;
;; height ;;
;;;;;;;;;;;;
(defgeneric height (clog-obj)
(:documentation "Get/Setf html height in pixels."))
(defmethod height ((obj clog-obj))
(jquery-query obj "height()"))
(defgeneric set-height (clog-obj value)
(:documentation "Set height VALUE for CLOG-OBJ"))
(defmethod set-height ((obj clog-obj) value)
(jquery-execute obj (format nil "height('~A')" value)))
(defsetf height set-height)
;;;;;;;;;;;
;; width ;;
;;;;;;;;;;;
(defgeneric width (clog-obj)
(:documentation "Get/Setf html width in pixels."))
(defmethod width ((obj clog-obj))
(jquery-query obj "width()"))
(defgeneric set-width (clog-obj value)
(:documentation "Set width VALUE for CLOG-OBJ"))
(defmethod set-width ((obj clog-obj) value)
(jquery-execute obj (format nil "width('~A')" value)))
(defsetf width set-width)
;;;;;;;;;;;
;; focus ;;
;;;;;;;;;;;
(defgeneric focus (clog-obj)
(:documentation "Focus on CLOG-OBJ"))
(defmethod focus ((obj clog-obj))
(jquery-execute obj "focus()"))
;;;;;;;;;;
;; blur ;;
;;;;;;;;;;
(defgeneric blur (clog-obj)
(:documentation "Remove focus from CLOG-OBJ"))
(defmethod focus ((obj clog-obj))
(jquery-execute "blur()"))
;;;;;;;;;;;;;;;;;;
;; create-child ;;
;;;;;;;;;;;;;;;;;;
(defgeneric create-child (clog-obj html &key auto-place)
(:documentation "Create a new CLOG-OBJ from HTML element as child of OBJ and if :AUTO-PLACE (default t)
place-inside-bottom-of OBJ"))
(defmethod create-child ((obj clog-obj) html &key (auto-place t))
(let ((child (create-with-html (connection-id obj) html)))
(if auto-place
(place-inside-bottom-of obj child)
child)))
;;;;;;;;;;;;;;;;;;;;;
;; attach-as-child ;;
;;;;;;;;;;;;;;;;;;;;;
(defgeneric attach-as-child (clog-obj html-id)
(:documentation "Create a new CLOG-OBJ and attach an existing element with HTML-ID. The
HTML-ID must be unique."))
(defmethod attach-as-child ((obj clog-obj) html-id)
(cc:execute (connection-id obj) (format nil "clog['~A']=$('#~A')" html-id html-id))
(make-clog-obj (connection-id obj) html-id))
;;;;;;;;;;;;
;; validp ;;
;;;;;;;;;;;;
(defgeneric validp (clog-obj)
(:documentation "Returns true of connection is valid on this CLOG-OBJ."))
(defmethod validp ((obj clog-obj))
(cc:validp (connection-id obj)))
;;;;;;;;;;;;;;;;;;;;;
;; connection-data ;;
;;;;;;;;;;;;;;;;;;;;;
(defgeneric connection-data (clog-obj)
(:documentation "Get connection-data that is associated with
clog-obj that will persist regardless of thread."))
(defmethod connection-data ((obj clog-obj))
(cc:get-connection-data (connection-id obj)))
;;;;;;;;;;;;;;;;;
;; place-after ;;
;;;;;;;;;;;;;;;;;
(defgeneric place-after (clog-obj next-obj)
(:documentation "Places NEXT-OBJ after CLOG-OBJ in DOM"))
(defmethod place-after ((obj clog-obj) next-obj)
(jquery-execute obj (format nil "after(~A)" (script-id next-obj)))
next-obj)
;;;;;;;;;;;;;;;;;;
;; place-before ;;
;;;;;;;;;;;;;;;;;;
(defgeneric place-before (clog-obj next-obj)
(:documentation "Places NEXT-OBJ before CLOG-OBJ in DOM"))
(defmethod place-before ((obj clog-obj) next-obj)
(jquery-execute obj (format nil "before(~A)" (script-id next-obj)))
next-obj)
;;;;;;;;;;;;;;;;;;;;;;;;;
;; place-inside-top-of ;;
;;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric place-inside-top-of (clog-obj next-obj)
(:documentation "Places NEXT-OBJ inside top of CLOG-OBJ in DOM"))
(defmethod place-inside-top-of ((obj clog-obj) next-obj)
(jquery-execute obj (format nil "prepend(~A)" (script-id next-obj)))
next-obj)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; place-inside-bottom-of ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric place-inside-bottom-of (clog-obj next-obj)
(:documentation "Places NEXT-OBJ inside bottom of CLOG-OBJ in DOM"))
(defmethod place-inside-bottom-of ((obj clog-obj) next-obj)
(jquery-execute obj (format nil "append(~A)" (script-id next-obj)))
next-obj)
;;;;;;;;;;;;;;;;;;
;; set-on-focus ;;
;;;;;;;;;;;;;;;;;;
(defgeneric set-on-focus (clog-obj on-focus-handler)
(:documentation "Set the ON-FOCUS-HANDLER for CLOG-OBJ. If ON-FOCUS-HANDLER
is nil unbind the event."))
(defmethod set-on-focus ((obj clog-obj) on-focus-handler)
(let ((on-focus on-focus-handler))
(set-event obj "focus"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-focus)))))
;;;;;;;;;;;;;;;;;
;; set-on-blur ;;
;;;;;;;;;;;;;;;;;
(defgeneric set-on-blur (clog-obj on-blur-handler)
(:documentation "Set the ON-BLUR-HANDLER for CLOG-OBJ. If ON-BLUR-HANDLER
is nil unbind the event."))
(defmethod set-on-blur ((obj clog-obj) on-blur-handler)
(let ((on-blur on-blur-handler))
(set-event obj "blur"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-blur)))))
;;;;;;;;;;;;;;;;;;;
;; set-on-change ;;
;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-change (clog-obj on-change-handler)
(:documentation "Set the ON-CHANGE-HANDLER for CLOG-OBJ. If ON-CHANGE-HANDLER
is nil unbind the event."))
(defmethod set-on-change ((obj clog-obj) on-change-handler)
(let ((on-change on-change-handler))
(set-event obj "change"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-change)))))
;;;;;;;;;;;;;;;;;;;;;
;; set-on-focus-in ;;
;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-focus-in (clog-obj on-focus-in-handler)
(:documentation "Set the ON-FOCUS-IN-HANDLER for CLOG-OBJ. If
ON-FOCUS-IN-HANDLER is nil unbind the event."))
(defmethod set-on-focus-in ((obj clog-obj) on-focus-in-handler)
(let ((on-focus-in on-focus-in-handler))
(set-event obj "focusin"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-focus-in)))))
;;;;;;;;;;;;;;;;;;;;;;
;; set-on-focus-out ;;
;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-focus-out (clog-obj on-focus-out-handler)
(:documentation "Set the ON-FOCUS-OUT-HANDLER for CLOG-OBJ.
If ON-FOCUS-OUT-HANDLER is nil unbind the event."))
(defmethod set-on-focus-out ((obj clog-obj) on-focus-out-handler)
(let ((on-focus-out on-focus-out-handler))
(set-event obj "focusout"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-focus-out)))))
;;;;;;;;;;;;;;;;;;
;; set-on-reset ;;
;;;;;;;;;;;;;;;;;;
(defgeneric set-on-reset (clog-obj on-reset-handler)
(:documentation "Set the ON-RESET-HANDLER for CLOG-OBJ. If ON-RESET-HANDLER
is nil unbind the event."))
(defmethod set-on-reset ((obj clog-obj) on-reset-handler)
(let ((on-reset on-reset-handler))
(set-event obj "reset"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-reset)))))
;;;;;;;;;;;;;;;;;;;
;; set-on-search ;;
;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-search (clog-obj on-search-handler)
(:documentation "Set the ON-SEARCH-HANDLER for CLOG-OBJ. If ON-SEARCH-HANDLER
is nil unbind the event."))
(defmethod set-on-search ((obj clog-obj) on-search-handler)
(let ((on-search on-search-handler))
(set-event obj "search"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-search)))))
;;;;;;;;;;;;;;;;;;;
;; set-on-select ;;
;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-select (clog-obj on-select-handler)
(:documentation "Set the ON-SELECT-HANDLER for CLOG-OBJ. If ON-SELECT-HANDLER
is nil unbind the event."))
(defmethod set-on-select ((obj clog-obj) on-select-handler)
(let ((on-select on-select-handler))
(set-event obj "select"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-select)))))
;;;;;;;;;;;;;;;;;;;
;; set-on-submit ;;
;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-submit (clog-obj on-submit-handler)
(:documentation "Set the ON-SUBMIT-HANDLER for CLOG-OBJ. If ON-SUBMIT-HANDLER
is nil unbind the event."))
(defmethod set-on-submit ((obj clog-obj) on-submit-handler)
(let ((on-submit on-submit-handler))
(set-event obj "submit"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-submit)))))
;;;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-context-menu ;;
;;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-context-menu (clog-obj on-context-menu-handler)
(:documentation "Set the ON-CONTEXT-MENU-HANDLER for CLOG-OBJ. If
ON-CONTEXT-MENU-HANDLER is nil unbind the event. Setting
on-mouse-right-click will replace this handler."))
(defmethod set-on-context-menu ((obj clog-obj) on-context-menu-handler)
(let ((on-context-menu on-context-menu-handler))
(set-event obj "contextmenu"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-context-menu)))))
;;;;;;;;;;;;;;;;;;
;; set-on-click ;;
;;;;;;;;;;;;;;;;;;
(defgeneric set-on-click (clog-obj on-click-handler)
(:documentation "Set the ON-CLICK-HANDLER for CLOG-OBJ. If ON-CLICK-HANDLER
is nil unbind the event. Setting this event will replace an on-mouse click if
set."))
(defmethod set-on-click ((obj clog-obj) on-click-handler)
(let ((on-click on-click-handler))
(set-event obj "click"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-click)))))
;;;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-double-click ;;
;;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-double-click (clog-obj on-double-click-handler)
(:documentation "Set the ON-DOUBLE-CLICK-HANDLER for CLOG-OBJ. If
ON-DOUBLE-CLICK-HANDLER is nil unbind the event. Setting the
on-mouse-double-click event will replace this handler."))
(defmethod set-on-double-click ((obj clog-obj) on-double-click-handler)
(let ((on-double-click on-double-click-handler))
(set-event obj "dblclick"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-double-click)))))
;;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-click ;;
;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-click (clog-obj on-mouse-click-handler)
(:documentation "Set the ON-MOUSE-CLICK-HANDLER for CLOG-OBJ. If
ON-MOUSE-CLICK-HANDLER is nil unbind the event. Setting this event will replace
on an on-click event."))
(defmethod set-on-mouse-click ((obj clog-obj) on-mouse-click-handler)
(let ((on-mouse-click on-mouse-click-handler))
(set-event obj "click"
(lambda (data)
(funcall on-mouse-click (parse-mouse-event data)))
:call-back-script mouse-event-script)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-double-click ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-double-click (clog-obj on-mouse-double-click-handler)
(:documentation "Set the ON-MOUSE-DOUBLE-CLICK-HANDLER for CLOG-OBJ. If
ON-MOUSE-DOUBLE-CLICK-HANDLER is nil unbind the event. Setting this event will
replace on an on-double-click event."))
(defmethod set-on-mouse-double-click ((obj clog-obj) on-mouse-double-click-handler)
(let ((on-mouse-double-click on-mouse-double-click-handler))
(set-event obj "dblclick"
(lambda (data)
(funcall on-mouse-double-click (parse-mouse-event data)))
:call-back-script mouse-event-script)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-right-click ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-right-click (clog-obj on-mouse-right-click-handler)
(:documentation "Set the ON-MOUSE-RIGHT-CLICK-HANDLER for CLOG-OBJ. If
ON-MOUSE-RIGHT-CLICK-HANDLER is nil unbind the event. Setting this event will
replace on an on-context-menu event."))
(defmethod set-on-mouse-right-click ((obj clog-obj) on-mouse-right-click-handler)
(let ((on-mouse-right-click on-mouse-right-click-handler))
(set-event obj "contextmenu"
(lambda (data)
(funcall on-mouse-right-click (parse-mouse-event data)))
:call-back-script mouse-event-script)))
;;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-enter ;;
;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-enter (clog-obj on-mouse-enter-handler)
(:documentation "Set the ON-MOUSE-ENTER-HANDLER for CLOG-OBJ. If ON-MOUSE-ENTER-HANDLER
is nil unbind the event."))
(defmethod set-on-mouse-enter ((obj clog-obj) on-mouse-enter-handler)
(let ((on-mouse-enter on-mouse-enter-handler))
(set-event obj "mouseenter"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-mouse-enter)))))
;;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-leave ;;
;;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-leave (clog-obj on-mouse-leave-handler)
(:documentation "Set the ON-MOUSE-LEAVE-HANDLER for CLOG-OBJ. If ON-MOUSE-LEAVE-HANDLER
is nil unbind the event."))
(defmethod set-on-mouse-leave ((obj clog-obj) on-mouse-leave-handler)
(let ((on-mouse-leave on-mouse-leave-handler))
(set-event obj "mouseleave"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-mouse-leave)))))
;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-over ;;
;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-over (clog-obj on-mouse-over-handler)
(:documentation "Set the ON-MOUSE-OVER-HANDLER for CLOG-OBJ. If ON-MOUSE-OVER-HANDLER
is nil unbind the event."))
(defmethod set-on-mouse-over ((obj clog-obj) on-mouse-over-handler)
(let ((on-mouse-over on-mouse-over-handler))
(set-event obj "mouseover"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-mouse-over)))))
;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-out ;;
;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-out (clog-obj on-mouse-out-handler)
(:documentation "Set the ON-MOUSE-OUT-HANDLER for CLOG-OBJ. If ON-MOUSE-OUT-HANDLER
is nil unbind the event."))
(defmethod set-on-mouse-out ((obj clog-obj) on-mouse-out-handler)
(let ((on-mouse-out on-mouse-out-handler))
(set-event obj "mouseout"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-mouse-out)))))
;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-down ;;
;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-down (clog-obj on-mouse-down-handler)
(:documentation "Set the ON-MOUSE-DOWN-HANDLER for CLOG-OBJ. If
ON-MOUSE-DOWN-HANDLER is nil unbind the event."))
(defmethod set-on-mouse-down ((obj clog-obj) on-mouse-down-handler)
(let ((on-mouse-down on-mouse-down-handler))
(set-event obj "mousedown"
(lambda (data)
(funcall on-mouse-down (parse-mouse-event data)))
:call-back-script mouse-event-script)))
;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-up ;;
;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-up (clog-obj on-mouse-up-handler)
(:documentation "Set the ON-MOUSE-UP-HANDLER for CLOG-OBJ. If
ON-MOUSE-UP-HANDLER is nil unbind the event."))
(defmethod set-on-mouse-up ((obj clog-obj) on-mouse-up-handler)
(let ((on-mouse-up on-mouse-up-handler))
(set-event obj "mouseup"
(lambda (data)
(funcall on-mouse-up (parse-mouse-event data)))
:call-back-script mouse-event-script)))
;;;;;;;;;;;;;;;;;;;;;;;
;; set-on-mouse-move ;;
;;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-mouse-move (clog-obj on-mouse-move-handler)
(:documentation "Set the ON-MOUSE-MOVE-HANDLER for CLOG-OBJ. If
ON-MOUSE-MOVE-HANDLER is nil unbind the event."))
(defmethod set-on-mouse-move ((obj clog-obj) on-mouse-move-handler)
(let ((on-mouse-move on-mouse-move-handler))
(set-event obj "mousemove"
(lambda (data)
(funcall on-mouse-move (parse-mouse-event data)))
:call-back-script mouse-event-script)))
;;;;;;;;;;;;;;;;;;;;;;
;; set-on-character ;;
;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-character (clog-obj on-character-handler)
(:documentation "Set the ON-CHARACTER-HANDLER for CLOG-OBJ. If
ON-CHARACTER-HANDLER is nil unbind the event. Setting this event
will replace a on-key-press"))
(defmethod set-on-character ((obj clog-obj) on-character-handler)
(let ((on-character on-character-handler))
(set-event obj "keypress"
(lambda (data)
(let ((f (parse-keyboard-event data)))
(funcall on-character (code-char (getf f ':char-code)))))
:call-back-script keyboard-event-script)))
;;;;;;;;;;;;;;;;;;;;;
;; set-on-key-down ;;
;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-key-down (clog-obj on-key-down-handler)
(:documentation "Set the ON-KEY-DOWN-HANDLER for CLOG-OBJ. If
ON-KEY-DOWN-HANDLER is nil unbind the event."))
(defmethod set-on-key-down ((obj clog-obj) on-key-down-handler)
(let ((on-key-down on-key-down-handler))
(set-event obj "keydown"
(lambda (data)
(funcall on-key-down (parse-keyboard-event data)))
:call-back-script keyboard-event-script)))
;;;;;;;;;;;;;;;;;;;
;; set-on-key-up ;;
;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-key-up (clog-obj on-key-up-handler)
(:documentation "Set the ON-KEY-UP-HANDLER for CLOG-OBJ. If
ON-KEY-UP-HANDLER is nil unbind the event."))
(defmethod set-on-key-up ((obj clog-obj) on-key-up-handler)
(let ((on-key-up on-key-up-handler))
(set-event obj "keyup"
(lambda (data)
(funcall on-key-up (parse-keyboard-event data)))
:call-back-script keyboard-event-script)))
;;;;;;;;;;;;;;;;;;;;;;
;; set-on-key-press ;;
;;;;;;;;;;;;;;;;;;;;;;
(defgeneric set-on-key-press (clog-obj on-key-press-handler)
(:documentation "Set the ON-KEY-PRESS-HANDLER for CLOG-OBJ. If
ON-KEY-PRESS-HANDLER is nil unbind the event."))
(defmethod set-on-key-press ((obj clog-obj) on-key-press-handler)
(let ((on-key-press on-key-press-handler))
(set-event obj "keypress"
(lambda (data)
(funcall on-key-press (parse-keyboard-event data)))
:call-back-script keyboard-event-script)))
;;;;;;;;;;;;;;;;;
;; set-on-copy ;;
;;;;;;;;;;;;;;;;;
(defgeneric set-on-copy (clog-obj on-copy-handler)
(:documentation "Set the ON-COPY-HANDLER for CLOG-OBJ. If ON-COPY-HANDLER
is nil unbind the event."))
(defmethod set-on-copy ((obj clog-obj) on-copy-handler)
(let ((on-copy on-copy-handler))
(set-event obj "copy"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-copy)))))
;;;;;;;;;;;;;;;;
;; set-on-cut ;;
;;;;;;;;;;;;;;;;
(defgeneric set-on-cut (clog-obj on-cut-handler)
(:documentation "Set the ON-CUT-HANDLER for CLOG-OBJ. If ON-CUT-HANDLER
is nil unbind the event."))
(defmethod set-on-cut ((obj clog-obj) on-cut-handler)
(let ((on-cut on-cut-handler))
(set-event obj "cut"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-cut)))))
;;;;;;;;;;;;;;;;;;
;; set-on-paste ;;
;;;;;;;;;;;;;;;;;;
(defgeneric set-on-paste (clog-obj on-paste-handler)
(:documentation "Set the ON-PASTE-HANDLER for CLOG-OBJ. If ON-PASTE-HANDLER
is nil unbind the event."))
(defmethod set-on-paste ((obj clog-obj) on-paste-handler)
(let ((on-paste on-paste-handler))
(set-event obj "paste"
(lambda (data)
(declare (ignore data)) ; event has no data to decode
(funcall on-paste)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Implementation - clog
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;
;; initialize ;;
;;;;;;;;;;;;;;;;
(defvar *on-new-window* nil "Store the on-new-window handler")
(defun on-connect (id)
(when cc:*verbose-output*
(format t "Start new window handler on connection-id - ~A" id))
(let ((body (make-clog-obj id 0)))
(funcall *on-new-window* body)))
(defun initialize (on-new-window
&key
(host "0.0.0.0")
(port 8080)
(boot-file "/boot.html")
(static-root #P"./static-files/"))
"Inititalze CLOG on a socket using HOST and PORT to serve BOOT-FILE as
the default route to establish web-socket connections and static files
located at STATIC-ROOT."
(setf *on-new-window* on-new-window)
(cc:initialize #'on-connect
:host host
:port port
:boot-file boot-file
:static-root static-root))
;;;;;;;;;;;;;;
;; shutdown ;;
;;;;;;;;;;;;;;
(defun shutdown ()
"Shutdown CLOG."
(cc:shutdown-clog))
;;;;;;;;;;;;
;; attach ;;
;;;;;;;;;;;;
(defun attach (connection-id html-id)
"Create a new clog-obj and attach an existing element with HTML-ID on
CONNECTION-ID to it and then return it. The HTML-ID must be unique."
(cc:execute connection-id (format nil "clog['~A']=$('#~A')" html-id html-id))
(make-clog-obj connection-id html-id))
;;;;;;;;;;;;;;;;;;;;;;
;; create-with-html ;;
;;;;;;;;;;;;;;;;;;;;;;
(defun create-with-html (connection-id html)
"Create a new clog-obj and attach it to HTML on CONNECTION-ID. There must be
a single outer block that will be set to an internal id. The returned clog-obj
requires placement or will not be visible, ie. place-after, etc"
(let ((web-id (cc:generate-id)))
(cc:execute
connection-id
(format nil "clog['~A']=$(\"~A\"); clog['~A'].first().prop('id','~A')"
web-id html web-id web-id))
(make-clog-obj connection-id web-id)))
;;;;;;;;;;;;;;;
;; js-true-p ;;
;;;;;;;;;;;;;;;
(defun js-true-p (value)
"Return true if VALUE equalp the string true"
(equalp value "true"))
;;;;;;;;;;;;;;;;;;
;; open-browser ;;
;;;;;;;;;;;;;;;;;;
(defun open-browser (&key (url "http://127.0.0.1:8080"))
"Open a web browser to URL."
(trivial-open-browser:open-browser url))