New patches: [Adding tal and ajax features. burban@opopop.net**20090504203906] { adddir ./src/ucw-ajax adddir ./src/ucw-tal hunk ./src/backend/mod-lisp.lisp 3 -(in-package :it.bese.ucw) +(in-package :it.bese.ucw.core) hunk ./src/backend/mod-lisp.lisp 45 - (setf (remote-address request) value)) + (setf (remote-address request) + (let ((ip-as-string value)) + (when ip-as-string + (let* ((real-remote-address (first (cl-ppcre:split "," ip-as-string :sharedp t))) + (pieces (cl-ppcre:split "\\." real-remote-address :sharedp t))) + (declare (type list pieces)) + (if (= (length pieces) 4) + (iter (with result = (make-array 4 :element-type '(unsigned-byte 8))) + (for idx :from 0 :below 4) + (for ip-address-part = (parse-integer (pop pieces))) + (assert (<= 0 ip-address-part 255)) + (setf (aref result idx) ip-address-part) + (finally (return result))) + (progn + (ucw.backend.info "Returning NIL instead of an invalid ip address: ~S" ip-as-string) + nil))))) + )) hunk ./src/core-package.lisp 149 + #:visiblep + #:dirtyp hunk ./src/rerl/request-loop-error.lisp 152 - (format-rfc3339-timestring nil (now)) + (format-rfc3339-timestring (now)) hunk ./src/rerl/standard-session-frame.lisp 41 - -(defmacro register-ajax-action ((&rest args &key (class ''ajax-action) &allow-other-keys) &body body) - (remf-keywords args :class) - `(register-action (:class ,class ,@args) - ,@body)) addfile ./src/ucw-ajax/ajax-actions.lisp hunk ./src/ucw-ajax/ajax-actions.lisp 1 +;; -*- lisp -*- + +(in-package :ucw-ajax) + +(enable-bracket-syntax) + +(declaim (inline action-ajax-p)) +(defun action-ajax-p (action) + (typep action 'ajax-action)) + +(defmethod compute-url ((action ajax-action) (app application)) + (let ((uri (call-next-method))) + (setf (uri.path uri) (strcat (uri.path uri) it.bese.ucw.core::+ajax-action-dispatcher-url+)) + uri)) + +(defmethod handle-toplevel-condition ((application application) (error serious-condition) (action ajax-action)) + ;;(log-error-with-backtrace error) + (abort-action "Internal server error")) + +(defmacro handle-ajax-request ((&key (succesful-when-finishes t) (output-yaclml-stream-on-failure nil)) &body body) + (with-unique-names (yaclml-body) + `(progn + (setf (get-header *response* "Status") +http-ok+ + (get-header *response* "Content-Type") "text/xml") + (<:as-is #.(format nil "~%")) + {with-xml-syntax + ) + (progn + (it.bese.ucw.core::ucw.rerl.ajax.debug "Failed to render ajax answer, error message is ~S" -message-) + ,(when output-yaclml-stream-on-failure + `(<:as-is ,yaclml-body)) + + (when -message- + )))))>}))) + +(defmethod call-action ((action ajax-action) application session frame) + "Wrap the ajax action's output in an XML document. The action is free to render +any valid XML body that can be processed on the client side." + ;; TODO, attila: the encoding in the default xml header should be taken from (encoding (context.response *context*)) + ;; is there a function that converts to the appropiate format? + (handle-ajax-request (:succesful-when-finishes nil) + (restart-case + (let ((swank::*sldb-quit-restart* 'abort-action)) + (call-next-method) + (it.bese.ucw.core::ucw.rerl.actions.debug "The body of CALL-ACTION for AJAX-ACTION was successful, calling SEND-EVENTS-TO-THE-CLIENT") + ;; make sure we don't send partial content in case of an error + (<:as-is + ;; TODO there could be a more efficient construct in yaclml for this based on (setf (fill-pointer ...) ...) + (with-yaclml-output-to-string + (when (has-events-for-the-client session) + (send-events-to-the-client session)))) + (setf -successp- t)) + (abort-action (&optional (failure-message "Internal server error")) + :report "Abort processing this ajax action" + (it.bese.ucw.core::ucw.rerl.actions.debug "Ajax ABORT-ACTION restart invoked with FAILURE-MESSAGE ~S" failure-message) + (if failure-message + (setf -message- failure-message) + (setf -successp- t)))))) + +(defmethod call-action :around ((action ajax-action) application session frame) + (let ((form (creation-time-current-form-of action))) + (if (and form + (parent form)) + (let ((*current-form* form)) + (it.bese.ucw.core::ucw.rerl.actions.dribble "Restored *CURRENT-FORM* to ~A from CALL-ACTION of AJAX-ACTION" *current-form*) + (call-next-method)) + (call-next-method)))) + +;; Copyright (c) 2003-2005 Edward Marco Baringer +;; All rights reserved. +;; +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions are +;; met: +;; +;; - Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; +;; - Redistributions in binary form must reproduce the above copyright +;; notice, this list of conditions and the following disclaimer in the +;; documentation and/or other materials provided with the distribution. +;; +;; - Neither the name of Edward Marco Baringer, nor BESE, nor the names +;; of its contributors may be used to endorse or promote products +;; derived from this software without specific prior written permission. +;; +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. addfile ./src/ucw-ajax/ajax-application.lisp hunk ./src/ucw-ajax/ajax-application.lisp 1 +;; See the file LICENCE for licence information. +(in-package :ucw-ajax) + +;;(enable-sharpquote<>-syntax) +(enable-bracket-syntax) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; dirty-component-tracking + +(defclass dirty-component-tracking-application-mixin () + () + (:documentation "Application mixin that enables the tracking of dirty components.")) + +(defclass dirty-component-tracking-session (standard-session) + ((dirty-components :initform (make-hash-table :weakness :key :test #'eq) + :accessor dirty-components-of))) + +(defmethod session-class list ((app dirty-component-tracking-application-mixin)) + 'dirty-component-tracking-session) + +(defmethod has-events-for-the-client ((session dirty-component-tracking-session)) + (if (next-method-p) + (call-next-method) + (iterate-visible-dirty-components + (lambda (component) + (declare (ignore component)) + (return-from has-events-for-the-client t))))) + +;;;;;;;;;;;;;;;;;;; +;; ajax-application + +(defclass ajax-application-mixin (dirty-component-tracking-application-mixin) + ()) + +(defclass standard-session (basic-session) + ()) + +(defclass ajax-session (dirty-component-tracking-session standard-session) + ((event-condition-variable :accessor event-condition-variable-of + :initform (when *supports-threads-p* + (make-condition-variable))) + (latest-polling-thread :accessor latest-polling-thread-of :initform nil))) + +(defmethod session-class list ((app ajax-application-mixin)) + 'ajax-session) + +(defgeneric notify-session-event (session) + (:method ((session ajax-session)) + (when *supports-threads-p* + (it.bese.ucw.core::ucw.rerl.ajax.debug "notify-session-event for session ~S in thread ~S" session (thread-name (current-thread))) + (condition-notify (event-condition-variable-of session))))) + +(defgeneric wait-for-session-event (session) + (:method ((session standard-session)) + (when *supports-threads-p* + (it.bese.ucw.core::ucw.rerl.ajax.debug "wait-for-session-event for session ~S, in thread ~S" session (thread-name (current-thread))) + (condition-wait (event-condition-variable-of session) (lock-of session))))) + +(defmethod send-events-to-the-client ((session ajax-session)) + (ajax-render-dirty-components)) + + +;;; +;;; Dirty stuff +;;; +(defvar %disable-dirtyness-tracking%) + +(defun it.bese.ucw.core::register-dirty-component (component) + (unless (boundp '%disable-dirtyness-tracking%) + (let ((session (session-of component))) + (when (typep session 'dirty-component-tracking-session) + (let ((table (dirty-components-of session))) + (it.bese.ucw.core::assert-session-lock-held session) + (when table + (it.bese.ucw.core::ucw.rerl.ajax.debug "About to register dirty component ~S, the session has ~S dirty components currently" + component (hash-table-count table)) + (setf (gethash component table) t) + (notify-session-event session))))))) + +(defun it.bese.ucw.core::unregister-dirty-component (component) + (let ((session (session-of component))) + (when (typep session 'dirty-component-tracking-session) + (let ((table (dirty-components-of session))) + (it.bese.ucw.core::assert-session-lock-held session) + (when table + (remhash component table) + (it.bese.ucw.core::ucw.rerl.ajax.debug "Component ~S is not dirty anymore, the session has ~S dirty components currently" + component (hash-table-count table))))))) + +(defmacro without-dirtyness-tracking (&body body) + "Disable dirtyness tracking. IOW, register-dirty-component will have no effects +while in the dynamic scope of without-dirtyness-tracking." + `(let ((%disable-dirtyness-tracking% t)) + ,@body)) + +(defun mark-dirty (component) + "It's a (setf (dirtyp component) t) inside a with-lock-held-on-session for convenience." + ;; TODO assert that locking another session is unsafe + (with-lock-held-on-session (session-of component) + (setf (dirtyp component) t))) + +(defun iterate-visible-dirty-components (visitor) + (it.bese.ucw.core::ucw.rerl.ajax.dribble "iterate-visible-dirty-components entered with visitor ~S" visitor) + (when-bind table (dirty-components-of (context.session *context*)) + (let ((components (hash-table-keys table))) + (it.bese.ucw.core::ucw.rerl.ajax.dribble "List of dirty components before collecting ~S" components) + (setf components (iter (for component in components) + (it.bese.ucw.core::ucw.rerl.ajax.dribble "Checking component ~S" component) + (unless (dirtyp component) + (it.bese.ucw.core::ucw.rerl.ajax.dribble "Component ~S is not dirty anymore, unregistering" component) + (it.bese.ucw.core::unregister-dirty-component component) + (next-iteration)) + (when (and (typep component 'ajax-component-mixin) + (not (has-ever-been-rendered-p component))) + (it.bese.ucw.core::ucw.rerl.ajax.dribble "The ajax component ~S has not been rendered, skipping it" component) + (next-iteration)) + (for (values visiblep distance) = (visiblep component)) + (if visiblep + (collect (cons component distance)) + (it.bese.ucw.core::ucw.rerl.ajax.dribble "Component ~S is not visible, dropping from the list" component)))) + (it.bese.ucw.core::ucw.rerl.ajax.dribble "List of dirty components before sorting ~S" components) + (setf components (sort components #'< :key #'cdr)) + (it.bese.ucw.core::ucw.rerl.ajax.dribble "List of dirty components after sorting ~S" components) + (iter (for (component . nil) in components) + (it.bese.ucw.core::ucw.rerl.ajax.debug "iterate-visible-dirty-components visiting component ~S, dirtyp? ~S" + component (dirtyp component)) + ;; need to check for dirtyness again, it might have been rendered meanwhile + (when (dirtyp component) + (it.bese.ucw.core::ucw.rerl.ajax.debug "iterate-visible-dirty-components calling visitor with component ~S" component) + (funcall visitor component)))))) + +(defmethod delete-session :after (application (session ajax-session)) + ;; abort the client poller, if there's any + (it.bese.ucw.core::assert-session-lock-held session) + (setf (latest-polling-thread-of session) nil) + (notify-session-event session)) + +;; (defvar *default-polling-delay* 3000 +;; "The default delay in ms to wait before the client connects the server again for new events.") +;; (defvar *max-number-of-live-polling-connections* 30 +;; "While there are less then this many polling connections, they are blocked on the server and woke up when an event is available.") +;; (defparameter *current-number-of-live-polling-connections* 0) + +;; (defun calculate-client-polling-delay () +;; (if (and *supports-threads-p* +;; (< *current-number-of-live-polling-connections* +;; *max-number-of-live-polling-connections*)) +;; 0 +;; *default-polling-delay*)) + +;; (defgeneric handle-polling-of-session (application session frame) +;; (:documentation "Called by the polling-dispatcher. SESSION and FRAME may be nil +;; when a polling request was received in an unknown session.") +;; (:method ((application ajax-application-mixin) session frame) +;; (if (and session frame) +;; (with-lock-held-on-session session +;; (ucw.rerl.ajax.debug "handle-polling-of-session entered while there are ~S alive pollers" +;; *current-number-of-live-polling-connections*) +;; (incf *current-number-of-live-polling-connections*) +;; (unwind-protect +;; (progn +;; (let ((current-thread (current-thread))) +;; (setf (latest-polling-thread-of session) current-thread) +;; (notify-session-event session) ; wake up any previous pollers to make them quit +;; (when (and *supports-threads-p* +;; (not (has-events-for-the-client session))) +;; (ucw.rerl.ajax.debug "client-polling-handler got nil from has-events-for-the-client, falling asleep") +;; (loop named waiting do +;; (wait-for-session-event session) ; we release the session lock and wait for a notification +;; (unless (eq (latest-polling-thread-of session) +;; current-thread) +;; (ucw.rerl.ajax.debug "client-poller aborting because there's a newer polling thread") +;; (notify-session-event session) ; wake up any other (possible poller) threads waiting +;; (return-from handle-polling-of-session)) +;; (when (has-events-for-the-client session) +;; (return-from waiting)))) +;; (ucw.rerl.ajax.debug "client-poller woke up, sending back the ajax answer") +;; ;; go through the public protocol, so transactions and stuff is alive while serving polling requests +;; (handle-action (make-action +;; (lambda () +;; (})))) +;; :sync ,',sync +;; :handler ,,(if handler +;; handler +;; ``ucw.io.default-js-to-lisp-rpc-handler) +;; :content (create :values evaluated-js-values) +;; ,,@options)))))))) + +;; (defmacro js-to-lisp-rpc* (options args &body body) +;; "Just like js-to-lisp-rpc but the arguments are transferred to lisp as-is. IOW, you can't give a js expression +;; that evaluates to the argument, but rather the named js variable will be visible on the lisp side." +;; `(js-to-lisp-rpc ,options ,args ,args ,@body)) addfile ./src/ucw-ajax/ajax-dispatchers.lisp hunk ./src/ucw-ajax/ajax-dispatchers.lisp 1 +;; -*- lisp -*- + +(in-package :ucw-ajax) + +;;;; ajax-action-dispatcher + +(defconstant +ajax-action-dispatcher-default-priority+ (- most-positive-fixnum 1001)) + +(defclass ajax-action-dispatcher (action-dispatcher ucw-standard::starts-with-matcher) + () + (:default-initargs :priority +ajax-action-dispatcher-default-priority+ + :url-string it.bese.ucw.core::+ajax-action-dispatcher-url+) + (:documentation "This is a specialized action dispatcher to handle ajax requests.")) + +(defmethod matcher-match ((matcher ajax-action-dispatcher) + (application basic-application) + (context standard-request-context)) + (it.bese.ucw.core::ucw.rerl.dispatcher.dribble "~S trying to match as ajax-action-dispatcher" matcher) + (when (starts-with (query-path-sans-prefix context) (url-string matcher)) + (multiple-value-bind (matchesp session frame action) (call-next-method) + (declare (ignore matchesp)) + (values t session frame action)))) + +(defmethod handler-handle ((dispatcher ajax-action-dispatcher) + (application basic-application) + (context standard-request-context) + matcher-result) + (destructuring-bind (session frame action) matcher-result + (disallow-response-caching (context.response context)) + (if (and session frame action) + (progn + (ensure-session application context session) + (handle-action action application session frame)) + (send-ajax-answer-to-expired-session)))) + +;; (defun send-ajax-answer-to-expired-session () +;; (handle-ajax-request (:succesful-when-finishes nil :output-yaclml-stream-on-failure t) +;; (within-xhtml-tag "error-handler" +;; ;; " #\Newline))))) + addfile ./src/ucw-ajax/ajax-package.lisp hunk ./src/ucw-ajax/ajax-package.lisp 1 +(in-package #:common-lisp-user) + +(defpackage #:ucw-ajax + (:use :ucw-core :ucw-standard :cl :yaclml :arnesi :js :iterate :bordeaux-threads) + (:shadowing-import-from :ucw-core :parent) + (:shadowing-import-from :js :new) + (:shadowing-import-from :iterate :in) + (:shadowing-import-from :iterate :with) + (:shadowing-import-from :iterate :while) + (:export + #:ajax-component-mixin + #:ajax-action + #:register-ajax-action + #:ajax-session + ;; ajax application + #:ajax-application-mixin + #:register-dirty-component + #:unregister-dirty-component + #:iterate-visible-dirty-components + )) + addfile ./src/ucw-ajax/ajax.lisp hunk ./src/ucw-ajax/ajax.lisp 1 +(in-package :ucw-ajax) + +(defun current-form () + (when (and (boundp 'it.bese.ucw.core::*current-form*) + it.bese.ucw.core::*current-form*) + it.bese.ucw.core::*current-form*)) + +;;;; ** The rendering protocol + +(defgeneric call-in-rendering-environment (trunk component) + (:documentation "This method can be used to set up the dynamic environment needed to render a component and all its children. +When ajax rendering all the parents are called with this method to get a chance to restore the dynamic environment.") + (:method-combination wrapping-standard)) + +(defmethod call-in-rendering-environment (trunk (component component)) + (funcall trunk)) + +(defmethod render :wrap-around ((component component)) + (call-in-rendering-environment #'call-next-method component) + (values)) + +(enable-bracket-syntax) + +(defmacro within-xhtml-tag (tag-name &body body) + "Execute BODY and wrap its yaclml output in a TAG-NAME xml node +with \"http://www.w3.org/1999/xhtml\" xml namespace." + `{with-xml-syntax + <(progn ,tag-name) :id "ucw-ajax" ;;:xmlns #.+xhtml-namespace-uri+ + ;;(@ "xmlns:dojo" #.+dojo-namespace-uri+) + ,@body>}) + +(defmacro within-dom-replacements-tag (&body body) + "Execute BODY and wrap its yaclml output in a dom-replacements xml node +with \"http://www.w3.org/1999/xhtml\" xml namespace. Client side js +iterates the elements of this node and replaces their counterparts +in the DOM tree with them." + `(within-xhtml-tag "div" + ,@body)) + +(defclass ajax-action (standard-action) + ((creation-time-current-form + :initform (current-form) + :accessor creation-time-current-form-of)) + (:metaclass mopp:funcallable-standard-class) + (:default-initargs :make-new-frame nil :call-render nil) + (:documentation "An ajax action in UCW is a raw action that renders an +XML document which is then processed by the client side js. The action +body may use yaclml tags and the WITH-XML-SYNTAX of yaclml to render +into the answer XML.")) + +(defmacro register-ajax-action ((&rest args &key (class ''ajax-action) &allow-other-keys) &body body) + (remf-keywords args :class) + `(register-action (:class ,class ,@args) + ,@body)) + +(defvar *ajax-component-being-rendered*) + +;; TODO get rid of this if/when the forthcoming dojo refactor makes it pointless +(defun currently-ajax-rendered-component () + (when (boundp '*ajax-component-being-rendered*) + *ajax-component-being-rendered*)) + +(defun ajax-rendering-in-progress-p () + (boundp '*ajax-component-being-rendered*)) + +;; all subclasses must be standard-component's because the dirtyp slot is needed by the ajax algorithms +(defcomponent ajax-component-mixin (html-element-mixin standard-component) + ((ucw-standard::dom-id :initform (js:gen-js-name-string :prefix "ajax")) ; override the initform of the inherited slot + (has-ever-been-rendered :initform nil :accessor has-ever-been-rendered-p) + (forbid-ajax-rendering :initform nil :accessor forbid-ajax-rendering-p :initarg :forbid-ajax-rendering-p + :documentation "This predicate my forbid AJAX rendering from this component and instruct the renderer to look further on the parent chain. The primary use of this is that sometimes (mostly due to browser rendring bugs) it's better to render bigger chunks of the page.")) + (:documentation "This is a marker class that marks a point in the component +chain from where a partial (AJAX) render may be started. The component +must render exactly one top-level DOM node and it must have an ID attribute. +The client side js will look up the DOM node identified by ID and replace it +with the freshly rendered one. + +Please note that this component in itself is not suitable for ajax +DOM node replacements because it does not render any wrapper nodes. +See WIDGET-COMPONENT for an ajax component that works on its own.")) + +(defparameter %ajax-stub-rendering-in-progress% nil + "Marks that we are going to render only a stub, so bail out in render :wrapping ajax-component-mixin.") + +(defgeneric render-ajax-stub (ajax-component-mixin) + (:method :around ((self ajax-component-mixin)) + (let ((%ajax-stub-rendering-in-progress% t)) + (call-next-method))) + (:method ((self ajax-component-mixin)) + (render self)) + (:documentation "Start rendering and stop at ajax-component-mixin boundaries. Only render a stub at those points (usually a <:div with an id) that can be later lazily replaced with an AJAX request.")) + +(defmethod render :after ((self ajax-component-mixin)) + (setf (has-ever-been-rendered-p self) t)) + +(defmethod render :wrapping ((self ajax-component-mixin)) + (unless %ajax-stub-rendering-in-progress% + (call-next-method))) + +(defgeneric ajax-render (component) + (:documentation "This method is called when we are rendering parts of the component hierarchy with AJAX. +By default it simply calls render after marking this fact on the ajax-component-mixin.") + (:method :around ((self ajax-component-mixin)) + (let ((*ajax-component-being-rendered* self)) + (call-next-method))) + (:method ((self ajax-component-mixin)) + (render self))) + +(defmacro in-restored-rendering-environment (component &body body) + `(call-in-restored-rendering-environment ,component + (lambda () + ,@body))) + +(defun call-in-restored-rendering-environment (component trunk) + (let ((parents)) + (iter (for parent :first component :then (parent parent)) + (while parent) + (push parent parents)) + (labels ((restorer () + ;; this is a nasty trick here: RESTORER is continously passed to the nested calls to + ;; CALL-IN-RENDERING-ENVIRONMENT until it pop'ped all the parents. then it finally calls + ;; AJAX-RENDER when all the parents have had a chance to restore the rendering environment. + (aif (pop parents) + (progn + (it.bese.ucw.core::ucw.rerl.info "Calling call-in-rendering-environment for ~S" it) + (call-in-rendering-environment #'restorer it)) + (progn + (it.bese.ucw.core::ucw.rerl.info "Environment of the parents is set up, calling trunk") + (call-in-rendering-environment trunk component))))) + (call-in-rendering-environment #'restorer (pop parents))))) + +(defun render-nearest-ajax-component (component) + (it.bese.ucw.core::ucw.rerl.debug "render-nearest-ajax-component from ~S" component) + (let ((ajax-component (iter (for current :first component :then (parent current)) + (while current) + (it.bese.ucw.core::ucw.rerl.info "Checking ~S" current) + (when (and (typep current 'ajax-component-mixin) + (not (forbid-ajax-rendering-p current))) + (return current)) + (while (slot-boundp current 'parent)) + (finally (return nil))))) + (it.bese.ucw.core::ucw.rerl.debug "render-nearest-ajax-component ended up at ~S" ajax-component) + (unless ajax-component + (error "No suitable ajax-component-mixin was found while walking the parent slots of ~A, unable to render AJAX answer" component)) + ;; we restore the env only up til the parent, because AJAX-RENDER + ;; calls RENDER which sets up the env of ajax-component itself. + (aif (parent ajax-component) + (call-in-restored-rendering-environment it (lambda () + (ajax-render ajax-component))) + (ajax-render ajax-component)))) + +(define-condition visible-dirty-component-remained (error) + ((component :initarg :component :accessor component-of)) + (:report (lambda (c stream) + (format stream "A visible dirty component ~A remained in session ~A after calling ajax-render-dirty-components. This would lead to a constant ajax rerendering in the poller. Make sure you either render all connected components or detach them!" + (component-of c) (session-of (component-of c)))))) + +(defmethod handle-toplevel-condition :around (application + (error visible-dirty-component-remained) + (action ajax-action)) + (when (debug-on-error application) + (invoke-slime-debugger-if-possible error)) + ;; when we are not debugging, just remove dirtyness and continue normal operation + (continue)) + +(defun ajax-render-dirty-components () + (within-dom-replacements-tag + (flet ((render-dirty-ajax-component (component) + (it.bese.ucw.core::ucw.rerl.debug "ajax-render-dirty-components at component ~S" component) + (render-nearest-ajax-component component))) + (iterate-visible-dirty-components #'render-dirty-ajax-component)) + (flet ((check-for-remained-dirty-component (c) + (when (visiblep c) + (restart-case + (error 'visible-dirty-component-remained :component c) + (continue () + :report "Remove dirtyness and leave me alone..." + (setf (dirtyp c) nil)))))) + (iterate-visible-dirty-components #'check-for-remained-dirty-component)))) + hunk ./src/ucw-standard/standard-tags.lisp 95 - (let ((reader (or reader accessor)) - (writer (or writer `(lambda (v) + (let ((writer (or writer `(lambda (v) addfile ./src/ucw-tal/tal-package.lisp hunk ./src/ucw-tal/tal-package.lisp 1 +(in-package #:common-lisp-user) + +(defpackage #:ucw-tal + (:use :ucw-core :cl :yaclml :arnesi) + (:shadowing-import-from :ucw-core :parent) + (:export + #:application-with-tal-support-mixin + #:template-component + #:render-template)) + addfile ./src/ucw-tal/template.lisp hunk ./src/ucw-tal/template.lisp 1 +;; -*- lisp -*- + +(in-package :ucw-tal) + +(defclass application-with-tal-support-mixin (application) + ((tal-generator :accessor application.tal-generator + :initarg :tal-generator + :documentation "A tal-generator object used to +lookup and compile tal pages for template-components."))) + +;;;; ** Template + +(defclass template-component (component) + ((template-name :accessor template-component.template-name + :initarg :template-name + :initform nil)) + (:documentation "Component which is rendered via a TAL template.")) + +(defgeneric template-component-environment (component) + (:documentation "Create the TAL environment for rendering COMPONENT's template. + +Methods defined on this generic function must return a TAL +environment: a list of TAL binding sets (see the documentation +for YACLML:MAKE-STANDARD-TAL-ENVIRONMENT for details on TAL +environments.)") + (:method-combination nconc)) + +(defmethod template-component-environment nconc ((component template-component)) + "Create the basic TAL environment. + +Binds the symbol ucw:component to the component object itself, +also puts the object COMPONENT on the environment (after the +binding of ucw:component) so that slots are, by default, +visable." + (make-standard-tal-environment `((component . ,component)) component)) + +(defmethod render :around ((component template-component)) + "Render a template based component. + +The name of the template is the value returned by the generic function +TEMPLATE-COMPONENT.TEMPLATE-NAME, the template will be rendered +in the environment returned by the generic function +TEMPLATE-COMPONENT-ENVIRONMENT." + (aif (template-component.template-name component) + (render-template *context* + it + (list* `((next-method-of-render . ,#'call-next-method)) + (template-component-environment component))) + (call-next-method))) + +(defcomponent standard-template-component (simple-template-component + widget-component) + ()) + +(defcomponent standard-template-component-with-body (standard-template-component + component-body-mixin) + ()) + +(defcomponent simple-template-component (template-component) + ((environment :initarg :environment :initform nil))) + +(defcomponent simple-template-component-with-body (simple-template-component component-body-mixin) + ()) + +(defmethod template-component-environment nconc ((component simple-template-component)) + (copy-list (slot-value component 'environment))) + +;; TODO these should be moved somewhere, renamed, or deleted completly +(defmacro show (page-name &rest environment) + `(call 'simple-template-component + :template-name ,page-name + :environment (tal-env ,@environment))) + +(defmacro show-window (page-name &rest environment) + `(call 'basic-window-template-component + :template-name ,page-name + :environment (tal-env ,@environment))) + +;; Copyright (c) 2003-2005 Edward Marco Baringer +;; All rights reserved. +;; +;; Redistribution and use in source and binary forms, with or without +;; modification, are permitted provided that the following conditions are +;; met: +;; +;; - Redistributions of source code must retain the above copyright +;; notice, this list of conditions and the following disclaimer. +;; +;; - Redistributions in binary form must reproduce the above copyright +;; notice, this list of conditions and the following disclaimer in the +;; documentation and/or other materials provided with the distribution. +;; +;; - Neither the name of Edward Marco Baringer, nor BESE, nor the names +;; of its contributors may be used to endorse or promote products +;; derived from this software without specific prior written permission. +;; +;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. addfile ./src/ucw-tal/yaclml.lisp hunk ./src/ucw-tal/yaclml.lisp 1 +;;;; -*- lisp -*- + +(in-package :ucw-tal) + +(defmethod render-template ((context request-context) template-name environment) + (let ((*print-pretty* nil)) + (it.bese.ucw.core::ucw.component.render.dribble ;;it.bese.ucw.core::ucw.rerl.info + "Rendering template ~S in environment ~S" template-name environment)) + (if-bind generator + (application.tal-generator (context.application context)) + (if-bind truename + (template-truename generator template-name) + (%render-template context generator truename environment) + (progn + (cerror "Retry rendering the template." "Can't find a template named ~S." template-name) + (render-template context template-name environment))) + (error "No known generator for the current application."))) + +(defun %render-template (context generator truename environment) + (let ((yaclml:*uri-to-package* (cons (cons "http://common-lisp.net/project/ucw/core" + (find-package :it.bese.ucw.tags)) + yaclml:*uri-to-package*))) + (restart-case + (funcall (load-tal generator truename) environment generator) + (retry () + :report (lambda (stream) + (format stream "Retry rendering ~A." truename)) + (return-from %render-template (%render-template context generator truename environment))))) + (it.bese.ucw.core::ucw.component.render.dribble "Template rendered.")) + +(defmethod preprocess-template (template-name environment &optional (application *default-application*)) + (aif (application.tal-generator application) + (if-bind truename (template-truename it template-name) + (let ((yaclml:*uri-to-package* (cons (cons "http://common-lisp.net/project/ucw/core" + (find-package :it.bese.ucw.tags)) + yaclml:*uri-to-package*))) + (yaclml::preprocess-tal it truename)) + (progn + (cerror "Retry rendering the template." "Can't find a template named ~S." template-name) + (render-template *context* template-name environment))) + (error "No known generator for the current application."))) + +;; TODO unused, delme? +(defun add-session-id (url) + "Add a session-id parametet to URL unless one is already present." + (when (position #\# url) + (error "Can't handle ~S. + +Adding session ids to links with section parts is not yet supported." + url)) + (flet ((already-have-session-id () + (let ((param-name-offset (search +session-parameter-name+ url))) + (if param-name-offset + (let ((=-offset (+ param-name-offset + (length +session-parameter-name+)))) + (and (< =-offset (length url)) + (char= #\= (aref url =-offset)))) + nil)))) + (if (already-have-session-id) + url + (with-output-to-string (new-url) + (write-sequence url new-url) + (if (position #\? url) + (write-char #\& new-url) + (write-char #\? new-url)) + (write-sequence +session-parameter-name+ new-url) + (write-char #\= new-url) + (write-sequence (session.id (context.session *context*)) new-url))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; Copyright (c) 2003-2005 Edward Marco Baringer +;;; All rights reserved. +;;; +;;; Redistribution and use in source and binary forms, with or without +;;; modification, are permitted provided that the following conditions are +;;; met: +;;; +;;; - Redistributions of source code must retain the above copyright +;;; notice, this list of conditions and the following disclaimer. +;;; +;;; - Redistributions in binary form must reproduce the above copyright +;;; notice, this list of conditions and the following disclaimer in the +;;; documentation and/or other materials provided with the distribution. +;;; +;;; - Neither the name of Edward Marco Baringer, nor BESE, nor the names +;;; of its contributors may be used to endorse or promote products +;;; derived from this software without specific prior written permission. +;;; +;;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +;;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +;;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +;;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +;;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +;;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +;;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +;;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +;;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +;;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +;;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. hunk ./ucw.asd 46 +(defsystem* :ucw-tal + :description "UncommonWeb : Tal Components" + :long-description "Contains the tal template extension." + :author "Marco Baringer " + :maintainer "Drew Crampsie " + :licence "BSD (sans advertising clause)" + :version "0.9" + :class ucw-system + :components + ((:module :src + :components + ((:module :ucw-tal + :components ((:file "tal-package") + (:file "template" :depends-on ("tal-package")) + (:file "yaclml" :depends-on ("template"))))))) + :properties ((version "0.9")) + :depends-on (:ucw)) hunk ./ucw.asd 64 +(defsystem* :ucw-ajax + :description "UncommonWeb : Ajax Components" + :long-description "Contains the ajax extension." + :author "Marco Baringer " + :maintainer "Drew Crampsie " + :licence "BSD (sans advertising clause)" + :version "0.9" + :class ucw-system + :components + ((:module :src + :components + ((:module :ucw-ajax + :components ((:file "ajax-package") + (:file "ajax" :depends-on ("ajax-package")) + (:file "ajax-application" :depends-on ("ajax")) + (:file "ajax-dispatchers" :depends-on ("ajax-application")) + (:file "ajax-actions" :depends-on ("ajax-dispatchers")) + ))))) + :properties ((version "0.9")) + :depends-on (:ucw :parenscript)) } Context: [M-q FTW! drewc@tech.coop**20090103194106] [Adding example code drewc@tech.coop**20090102234730] [Changes for new manual .. and manual itself-ish drewc@tech.coop**20090102234551] [emo fixes and more manual. drewc@tech.coop**20081204044706] [Add demo fixes. preliminary manual, nuck some old unused files. drewc@tech.coop**20081203012326] [Prepend url-prefix to window external stylesheets clinton@unknownlamer.org**20081202212435] [Move APPLICATION-WITH-WWW-ROOTS-MIXIN to ucw-standard clinton@unknownlamer.org**20081202095203 * Creates static file entry points at application init rather than relying upon the backend to have special file serving handler code ] [Import container classes into ucw-standard clinton@unknownlamer.org**20081202092252] [Move SERVE-{SEQUENCE|STREAM|FILE} to ucw-standard clinton@unknownlamer.org**20081202091517 * Export more of the response protocol * SERVE-FILE now returns a 404 page if the file does not exist ] [Reimplement starts-with-dispatcher clinton@unknownlamer.org**20081202090909 Uses ARNESI:STARTS-WITH to match a prefix; the suffix is available within the dispatcher body as *DISPATCHER-URL-SUFFIX* ] [Clean up core package definition clinton@unknownlamer.org**20081202073148 * Duplicated symbols removed * Symbols for things no longer in ucw-core removed * Export list is now coherently sorted; some additional sorting would be useful ] [Export http status codes from ucw-core clinton@unknownlamer.org**20081202064725] [Move JUMP and CALL-AS-WINDOW into ucw-standard clinton@unknownlamer.org**20081202041645] [Remove obsolete call macros clinton@unknownlamer.org**20081202041601] [Port html-element and widget-component to ucw-standard clinton@unknownlamer.org**20081126203441 * html-element -> html-element-mixin * Prefixed accessors with `html-element.' * widget[-inline]-component -> html-{block|inline}-element-mixin * Add UNIQUE-DOM-KEY to generate dom-ids as a drop-in replacement for js:gen-js-name-string ] [Remove ajax-appliction-mixin clinton@unknownlamer.org**20081126202754] [Port cookie-session-application-mixin to ucw-standard clinton@unknownlamer.org**20081126202737] [Remove some obsolete exports from core-package clinton@unknownlamer.org**20081126202444] [Port regexp and simple dispatcher to ucw-standard clinton@unknownlamer.org**20081126202054 * Export matcher/handler/dispatcher protocol from ucw-core * Remove ajax/parenscript/etc. dispatchers ] [Update iolib backend for more modern net.sockets clinton@unknownlamer.org**20081126063127] [Export MAKE-PLACE clinton@unknownlamer.org**20081126045512] [Remove CALL-IN-RENDERING-ENVIRONMENT clinton@unknownlamer.org**20081126042008 This would be useful in a theoretical future ucw-ajax extension, but is not useful for anything in the core or standard packages. ] [Remove obsolete constants clinton@unknownlamer.org**20081126041632] [Export REFRESH-COMPONENT clinton@unknownlamer.org**20081126041615] [Export CREATE-SERVER clinton@unknownlamer.org**20081126041608] [Nickname ucw-standard package ucw clinton@unknownlamer.org**20081124214150] [Initial exports for ucw-standard package clinton@unknownlamer.org**20081124214126] [Properly call with format-rfc3339-timestring with destination argument clinton@unknownlamer.org**20081124214013] [Fix invalid html and html escape arguments in send-redirect clinton@unknownlamer.org**20081124074610] [Added forgotten demo drewc@tech.coop**20081127210055 This is the demo. It will eventually be a demonstration of all the ucw components, and also serve as the example code for documentation purposes. ] [I forgot the .asd ... not the best start! drewc@tech.coop**20081118222013] [Remove outdated files no longer relevant for ucw-core. drewc@tech.coop**20081118214724] [Adding new files for ucw-core split. drewc@tech.coop**20081118211410] [UCW-CORE fork part 1. drewc@tech.coop**20081103191746 This patch is the start of the ucw-core fork, done in parts to appease the darcs gods. ] [add safari support to import-ajax-received-xhtml-node Sasha Kovar **20080419055600] [follow bind's values -> :values changes attila.lendvai@gmail.com**20080415155617] [fix lang:timestamp attila.lendvai@gmail.com**20080415155606] [temp fix for ip-address= until iolib's infrastructure is used attila.lendvai@gmail.com**20080415155521] [fix corner case in lang:timestamp attila.lendvai@gmail.com**20080208125533] [lang:timestamp is not in relative-mode by default attila.lendvai@gmail.com**20080208125401] [fix: added missing setup-readtable call to l10n.lisp attila.lendvai@gmail.com**20080207112636] [fix up ucw.lang:timestamp attila.lendvai@gmail.com**20080205233800] [fix cache purging of i18n-parenscript-dispatcher attila.lendvai@gmail.com**20080204234112] [support :relative-mode in lang::timestamp<> attila.lendvai@gmail.com**20080204233729] [follow iolib changes. patch by the man himself: Stelian Ionescu! :) attila.lendvai@gmail.com**20080202213406] [cleanup temp file handling, make sure no file is overridden attila.lendvai@gmail.com**20080123084343] [Make file upload root configurable clinton@unknownlamer.org**20080117023522] [Update iolib backend startup code clinton@unknownlamer.org**20080130222426] [weak try to fix the bitrotten examples attila.lendvai@gmail.com**20080111064758] [Add maxlength attribute to in-field-string-field render method. Sasha Kovar **20071228024947] [Add hidden-field html form field type. Sasha Kovar **20071228030046] [fix: (lang::timestamp ... :relative-mode #f) attila.lendvai@gmail.com**20071214150456] [Fix logging the backtrace of errors of ajax actions attila.lendvai@gmail.com**20071115211841] [Change make-new-session so that it calls make-instance with the :id initarg, so the name of the session's lock contains the session id. attila.lendvai@gmail.com**20071115211643] [change call-in-rendering-environment's arg order to follow the call-* conventions attila.lendvai@gmail.com**20071110145556] [add with-thread-name to handle-action attila.lendvai@gmail.com**20071105123555] [send a start message to the new acceptor attila.lendvai@gmail.com**20071105093338] [KLUDGE: do not read the request if the server is overloaded attila.lendvai@gmail.com**20071105093332] [prefix the various urls of window with the application's url-prefix attila.lendvai@gmail.com**20071102112232] [added with-session-specific-temporary-file and open-session-specific-temporary-file attila.lendvai@gmail.com**20071101151614] [added download-action-href-body attila.lendvai@gmail.com**20071101123136] [added bind dependency attila.lendvai@gmail.com**20071101123114] [more cl-def usage attila.lendvai@gmail.com**20071101122942] [added assert to find-session attila.lendvai@gmail.com**20071101034315] [more lock-held asserts attila.lendvai@gmail.com**20071031231609] [added log-ignorable-error-backtrace attila.lendvai@gmail.com**20071031231600] [housekeeping attila.lendvai@gmail.com**20071031231318] [rename httpd threads attila.lendvai@gmail.com**20071031231233] [refactor app/session locking and session expiration. attila.lendvai@gmail.com**20071031230717 TODO left: sessions are not purged, user code must call remove-expired-sessions periodically for now ] [invert the default value of call-as-response-handler attila.lendvai@gmail.com**20071031161408] [turn the assert-application-lock-held into a with-lock-held-on-application in delete-session attila.lendvai@gmail.com*-20071031005222] [housekeeping attila.lendvai@gmail.com**20071030163423] [fix importing toplevel script nodes from ajax answers attila.lendvai@gmail.com**20071031013401] [turn the assert-application-lock-held into a with-lock-held-on-application in delete-session attila.lendvai@gmail.com**20071031005222] [rework remove-expired-sessions a bit attila.lendvai@gmail.com**20071030211425] [added maybe-remove-expired-sessions attila.lendvai@gmail.com**20071030202921] [added clear-session-frame-history attila.lendvai@gmail.com**20071030200920] [also log the thread name in when the backtrace is logged attila.lendvai@gmail.com**20071030192729] [more fine-grained locking on the app/sessions when removing expired sessions attila.lendvai@gmail.com**20071030192337] [more usage of with-thread-name attila.lendvai@gmail.com**20071030163207] [renamed collapsible's "body" css class to "collapsible-body" attila.lendvai@gmail.com**20071029221941] [added a serve-stream and implemented serve-file using it attila.lendvai@gmail.com**20071029221842] [clear the request/response variables before trying to close the socket attila.lendvai@gmail.com**20071029140901] [use cl-def's with-macro for the with-*-lock-held macros to leave a function on the stack for easier debugging attila.lendvai@gmail.com**20071027142715] [fix define-js-resources C-cC-c'ing attila.lendvai@gmail.com**20071027000315] [drop single threaded httpd backend attila.lendvai@gmail.com**20071026193910] [drop aserve and araneida backends attila.lendvai@gmail.com**20071026191818] [drop araneida and aserve backends attila.lendvai@gmail.com**20071026190855] [baby steps towards dojo 0.9 attila.lendvai@gmail.com**20071026190751] [drop threaded-lisp-p and start-swank attila.lendvai@gmail.com**20071026190508] [use cl-def attila.lendvai@gmail.com**20071026190139] [renamed to *js-resource-registry-last-modified-at* attila.lendvai@gmail.com**20071026185951] [added cl-def dependency attila.lendvai@gmail.com**20071026184945] [drop the remainings of the old config stuff. attila.lendvai@gmail.com**20071026184851] [TAG before-controversial-refactor attila.lendvai@gmail.com**20071026182616] [disable file logging by default attila.lendvai@gmail.com**20071027003844] [added js side warning at 2/3 of the session timeout attila.lendvai@gmail.com**20071027000415] [change with-thread-name so that it appends to the current name attila.lendvai@gmail.com**20071026172638] [send a content-disposition by default in serve-file attila.lendvai@gmail.com**20071026172613] [clean up response sending in basic backend attila.lendvai@gmail.com**20071026165103] [use with-thread-name in call-with-ucw-error-handler attila.lendvai@gmail.com**20071026001437] [added with-thread-name, use it in the multithreaded-http-backend attila.lendvai@gmail.com**20071026000416] [make the progress indicator less opaque on first display attila.lendvai@gmail.com**20071025211356] [fix error handling related to raw requests. use the new headers-are-sent-p attila.lendvai@gmail.com**20071025211248] [added headers-are-sent-p attila.lendvai@gmail.com**20071025210053] [delete the session if it's found expired, add comment what's going on there attila.lendvai@gmail.com**20071024203306] [clean up error handling: when nested errors happen try to log it but let it go through for the upper levels attila.lendvai@gmail.com**20071024195032] [fix error handling attila.lendvai@gmail.com**20071024084530] [refactor session purging so that expired-p is only called from inside handle-action (so that use customizations on it are in effect) attila.lendvai@gmail.com**20071023203520] [added assert-session-lock-held attila.lendvai@gmail.com**20071023203433] [add the dojo debug container and the progress-container at on-load, so they get at the end of the doc. this makes it more google-friendly. attila.lendvai@gmail.com**20071023115001] [housekeeping attila.lendvai@gmail.com**20071022005238] [clean up script tag eval'ing in ajax answers attila.lendvai@gmail.com**20071021224724] [make that renders a localized representation of a local-time (using cl-l10n) -syntax to container.lisp attila.lendvai@gmail.com**20070709124550] [Fix current-form attila.lendvai@gmail.com**20070709120804] [Housekeeping attila.lendvai@gmail.com**20070709120750] [Change default :invocation-isolated of actions to be true attila.lendvai@gmail.com**20070709120705] [Fix decorate-with-invocation-id for url's without any parameters attila.lendvai@gmail.com**20070709120634] [Client-side mode for switching-container attila.lendvai@gmail.com**20070709120530] [Client side operation for collapsible-pane attila.lendvai@gmail.com**20070709120519] [Follow iolib changes in the iolib backend; added worker thread starting up to a max limit attila.lendvai@gmail.com**20070706235950] [Fix minor issues in the tests attila.lendvai@gmail.com**20070706235650] [Added with-unique-dom-ids attila.lendvai@gmail.com**20070706152552] [Fix :through-redirect of register-action when used with invocation isolated actions attila.lendvai@gmail.com**20070704225247] [Fix some #"" reader bugs attila.lendvai@gmail.com**20070704225142] [Move the render :wrap-around of template-component to simple :around for better customizability attila.lendvai@gmail.com**20070704224956] [Fix some issues with js-to-lisp-rpc attila.lendvai@gmail.com**20070704224814] [Export some cookie helpers, escape cookie values as uri, so you can have unicode stuff and cookie control chars in the value attila.lendvai@gmail.com**20070629231431 #:add-set-cookie-header #:cookie-value #:make-cookie ] [Fix uri escaping, the path was not propertly escaped causing problems with unicode url-prefix attila.lendvai@gmail.com**20070628163714] [Added make-cookie that calls escape-as-uri on the supplied path. This fixes session-cookie-application with unicode url-prefix. attila.lendvai@gmail.com**20070628163611] [Fix: bind *dispatcher-registers* as documented (but dispatchers needs a cleanup, probably based on lambda's and capturing) attila.lendvai@gmail.com**20070704193315] [Follow iolib changes (credit goes to Stelian Ionescu) attila.lendvai@gmail.com**20070629095410] [Added captcha support, which is a custom application that keeps an LRU queue of registered captchas and needs a captcha factory function attila.lendvai@gmail.com**20070625222440] [Housekeeping attila.lendvai@gmail.com**20070625221845] [Use -foo- naming convention for non-hygienic macro variables attila.lendvai@gmail.com**20070625221748] [Follow yaclml tal changes attila.lendvai@gmail.com**20070625221619] [Fix some dojo-widget stuff attila.lendvai@gmail.com**20070625220850] [Form field registration needs less js attila.lendvai@gmail.com**20070624191622] [Fix checkbox, add ucw-checkbox class to it attila.lendvai@gmail.com**20070624191611] [Follow iolib changes in the iolib backend, fix graceful shutdown of the backend attila.lendvai@gmail.com**20070624135356] [Fix even more issues with the l10n split. It turned out to be more headache then expected... attila.lendvai@gmail.com**20070622130815] [widget-component must be a full standard-component. use html-element and with-html-element-wrapper if you need a barebone class. attila.lendvai@gmail.com**20070622130602] [Fix start.lisp after the l10n split attila.lendvai@gmail.com**20070622104857] [Fix even more issues with the l10n split attila.lendvai@gmail.com**20070622104457] [Fix l10n separation attila.lendvai@gmail.com**20070622094959] [Use the ...-syntax naming convention attila.lendvai@gmail.com**20070622085859] [Finish the previous weak try to move l10n stuff into a system connection attila.lendvai@gmail.com**20070622085420 Move around some l10n definitions and add some dummies that are available even without l10n being loaded. ] [Mainly comments to some of the backend code attila.lendvai@gmail.com**20070622085310] [Move cl-l10n dependency into an asdf-system-connection attila.lendvai@gmail.com**20070621104846] [Make the test more iolib friendly attila.lendvai@gmail.com**20070618213713] [Added missing iolib backend stuff to the asd attila.lendvai@gmail.com**20070618213029] [Added an iolib backend attila.lendvai@gmail.com**20070618212142] [Make backend locking use a recursive lock attila.lendvai@gmail.com**20070618212120] [Renamed peer-address to remote-address as suggested by Cyrus Harmon attila.lendvai@gmail.com**20070618211655] [Fix the add some numbers example, follow form changes attila.lendvai@gmail.com**20070617134517] [Added dojo inline edit box example attila.lendvai@gmail.com**20070617134450] [Housekeeping attila.lendvai@gmail.com**20070617134339] [Support rendering a