;; -*- lisp -*- (in-package :it.bese.ucw) ;;;; ** Simple Window (defcomponent window-component () ((content-type :accessor window-component.content-type :initarg :content-type :initform nil ; default is text/html with charset from current application :documentation "The Content-Type header for the http response (also used in the meta tag)"))) (defmethod window-component.content-type :around ((window window-component)) "Either use slot value, or compute content-type from current application charset." (or (call-next-method) (setf (window-component.content-type window) (format nil "text/html~@[; charset=~A~]" (application.charset (context.application *context*)))))) (defmethod render :before ((window window-component)) (setf (get-header (context.response *context*) "Content-Type") (window-component.content-type window))) (defcomponent basic-window-features-mixin () ((title :accessor window-component.title :initarg :title :initform nil) (stylesheet :accessor window-component.stylesheet :initarg :stylesheet :initform nil :documentation "The URL of the css file to use as a stylesheet for this window.") (icon :accessor window-component.icon :initarg :icon :initform nil :documentation "Optional URL for an icon.") (doctype :accessor window-component.doctype :initarg :doctype :initform (load-time-value +xhtml-transitional-doctype+) :documentation "Doctype for this window.") (content-prologue :accessor window-component.content-prologue :initarg :content-prologue :initform nil :documentation "Unless nil it's printed <:as-is before any other output. Suitable for lines.") (html-tag-attributes :accessor window-component.html-tag-attributes :initarg :html-tag-attributes :initform (list "xmlns" #.+xhtml-namespace-uri+) :documentation "A yaclml attribute list that'll be rendered into the <:html tag's attributes.") (javascript :accessor window-component.javascript :initarg :javascript :initform nil :documentation "List of javascript includes. Each element must be a list whose first value is either the symbol :SRC or :JS. (:SRC url) - writes tag. (:JS form) - equivalent to (:SCRIPT (js:js* form)) (:SCRIPT string) - write . The elements will be rendered in order.")) (:documentation "A mixin that renders basic html toplevel tags.")) (defgeneric effective-window-stylesheets (window) (:documentation "This method is used to collect the effective stylesheet list for a window; available for customizations.") (:method-combination nconc) (:method nconc ((thing t)) (list)) (:method nconc ((window basic-window-features-mixin)) (copy-list (window-component.stylesheet window)))) (defmethod render ((window basic-window-features-mixin)) "This convience method assumes: 1) the stylesheet is external (as opposed to inlined) or is not used; 2) the script file is javascript and is external or is no script is used and 3) the title is either a literal or a lambda with one argument (the window)." (awhen (window-component.content-prologue window) (<:as-is it ~%)) (<:html :doctype (window-component.doctype window) (@ (window-component.html-tag-attributes window)) (render-html-head window) (render-html-body window))) (defgeneric render-html-head (window) (:method :around ((window basic-window-features-mixin)) (<:head (call-next-method))) (:method ((window basic-window-features-mixin)) (let* ((app (context.application *context*)) (url-prefix (application.url-prefix app))) (<:meta :http-equiv "Content-Type" :content (window-component.content-type window)) (awhen (window-component.title window) (<:title (if (functionp it) (funcall it window) (<:as-html it)))) (awhen (window-component.icon window) (<:link :rel "icon" :type "image/x-icon" :href (concatenate 'string url-prefix it))) (dolist (stylesheet (effective-window-stylesheets window)) (<:link :rel "stylesheet" :href stylesheet :type "text/css"))))) (defgeneric render-html-body (window) (:method :around ((window basic-window-features-mixin)) (<:body (render-window-scripts window) (call-next-method)))) (defgeneric render-window-scripts (window) (:method ((window basic-window-features-mixin)) (let* ((app (context.application *context*)) (url-prefix (application.url-prefix app))) (dolist* ((type value) (window-component.javascript window)) (ecase type (:src (<:script :type "text/javascript" :src (concatenate 'string url-prefix value) ;; most browsers (firefox, safari and ie at least) really, ;; really, really don't like empty script tags. The "" forces ;; yaclml to generate a seperate closing tag. "")) ;; TODO clean up these names (:js (" ~%)))))))) (defcomponent basic-window-component (basic-window-features-mixin window-component) () (:documentation "A convenience class for writing window components.")) (defcomponent dojo-window-component-mixin () ((dojo-debug :type boolean :accessor dojo-debug-p :initarg :dojo-debug) (dojo-debug-at-all-costs :type boolean :accessor dojo-debug-at-all-costs-p :initarg :dojo-debug-at-all-costs))) #+nil(defmethod render-html-head ((self dojo-window-component-mixin)) (call-next-method) (<:link :rel "stylesheet" :href "dijit/dijit.css" :type "text/css")) (defmethod dojo-debug-p :around ((self dojo-window-component-mixin)) (if (slot-boundp self 'dojo-debug) (call-next-method) (debug-on-error (context.application *context*)))) (defmethod dojo-debug-at-all-costs-p :around ((self dojo-window-component-mixin)) (if (slot-boundp self 'dojo-debug-at-all-costs) (call-next-method) (let ((app (context.application *context*))) (and (dojo-debug-p self) (and (typep app 'standard-application) (string= (javascript-log-level app) "debug")))))) (defun dojo-locale-name-for (locale) (declare (ignore locale)) (warn "dojo-locale-name-for should have been redefined by the cl-l10n integration")) ;; TODO propagate debug level to the dojo loggers. (defmethod initialize-instance :around ((self dojo-window-component-mixin) &key) ;; we use an :around to delay this code as late as possible, because the dojo scripts ;; must be the first thing in the document. (call-next-method) (let* ((app (context.application *context*)) (url-prefix (application.url-prefix app))) (setf (window-component.javascript self) (append (list (list :js (lambda () (let ((locale (context.locale *context*))) (when (consp locale) (setf locale (first locale))) `(setf dj-config (create ,@(when locale `(:locale ,(dojo-locale-name-for locale))) ,@(if (dojo-debug-p self) `(:is-debug true :debug-container-id "dojoDebug" ,@(when (dojo-debug-at-all-costs-p self) `(:debug-at-all-costs true))) `(:is-debug false)) :base-loader-uri ,(strcat url-prefix "static/dojo/")))))) '(:src "static/dojo/dojo.js") #+nil`(:script ,(js:js* `(progn ;; dojo.registerModulePath("dijit", "../dijit"); is not needed if dijit and dojo are sibling directories (dojo.require "dijit.util.parser")))) '(:src #.(map-to-dynamic-ucw-url "js/functional.js")) '(:src #.(map-to-dynamic-ucw-url "js/per-application.js"))) (when (dojo-debug-p self) `((:script ,(js:js* `(dojo.require "dojo.debug.console"))))) (window-component.javascript self))))) (defmethod render-window-scripts :after ((self dojo-window-component-mixin)) (when (dojo-debug-at-all-costs-p self) (" ;; (application.charset (context.application *context*))) :html-tag-attributes (list "xmlns" #.+xhtml-namespace-uri+ "xmlns:dojo" #.+dojo-namespace-uri+)) (:documentation "Window component that ensures a proper environment for all the UCW features. This window will load dojo and in general it will mess around with the browser environment. If you don't want that, use BASIC-WINDOW-COMPONENT.")) ;;; set up some UCW specific scripts (defmethod render-window-scripts :after ((self standard-window-component)) (