Stephen Compall avatar Stephen Compall committed c6674e0

Allow mark-dirty even while render-dirty-widgets is running [#22 state:resolved]

Comments (0)

Files changed (2)

src/request-handler.lisp

   (declare (special *dirty-widgets* *weblocks-output-stream*
 		    *before-ajax-complete-scripts* *on-ajax-complete-scripts*))
   (setf (content-type) *json-content-type*)
-  (let ((widget-alist (mapcar (lambda (w)
-				(cons
-				 (dom-id w)
-				 (progn
-				   (render-widget w :inlinep t)
-				   (get-output-stream-string *weblocks-output-stream*))))
-			      *dirty-widgets*)))
-    (format *weblocks-output-stream* "{\"widgets\":~A,\"before-load\":~A,\"on-load\":~A}"
-	    (encode-json-to-string widget-alist)
-	    (encode-json-to-string *before-ajax-complete-scripts*)
-	    (encode-json-to-string *on-ajax-complete-scripts*))))
+  (let ((render-state (make-hash-table :test 'eq)))
+    (labels ((circularity-warn (w)
+	       (style-warn 'non-idempotent-rendering
+		:change-made
+		(format nil "~S was marked dirty and skipped after ~
+			     already being rendered" w)))
+	     (render-enqueued (dirty)
+	       (loop for w in dirty
+		     if (gethash w render-state)
+		       do (circularity-warn w)
+		     else
+		       do (render-widget w :inlinep t)
+			  (setf (gethash w render-state) t)
+		       and collect (cons (dom-id w)
+					 (get-output-stream-string
+					     *weblocks-output-stream*))))
+	     (late-propagation-warn (ws)
+	       (style-warn 'report-non-idempotent-rendering
+		:change-made
+		(format nil "~A widgets were marked dirty" (length ws))))
+	     (absorb-dirty-widgets ()
+	       (loop for dirty = *dirty-widgets*
+		     while dirty
+		     count t into runs
+		     when (= 2 runs)
+		       do (late-propagation-warn dirty)
+		     do (setf *dirty-widgets* '())
+		     nconc (render-enqueued dirty))))
+      (format *weblocks-output-stream*
+	      "{\"widgets\":~A,\"before-load\":~A,\"on-load\":~A}"
+	      (encode-json-to-string (absorb-dirty-widgets))
+	      (encode-json-to-string *before-ajax-complete-scripts*)
+	      (encode-json-to-string *on-ajax-complete-scripts*)))))
 
 (defun action-txn-hook (hooks)
   "This is a dynamic action hook that wraps POST actions using the 

test/request-handler.lisp

 
 (in-package :weblocks-test)
 
+(deftestsuite request-handler-suite (weblocks-suite)
+  ())
+
 ;;; testing handle-client-request
 (deftest handle-client-request-0
     (with-request :get nil
 \"on-load\":[\"testjs\"]~
 }"))
 
+(defwidget dirtier ()
+  ((other :initarg :other)))
+
+(defmethod render-widget-body ((wij dirtier) &key &allow-other-keys)
+  (mark-dirty (slot-value wij 'other))
+  (princ 42 *weblocks-output-stream*))
+
+(addtest detect-dirty-circularity
+  (ensure-same (symbol-status 'dirtier) :internal)
+  (symbol-macrolet ((d weblocks::*dirty-widgets*))
+    (let* ((a (make-instance 'dirtier :dom-id "a"))
+	   (b (make-instance 'dirtier :other a :dom-id "b"))
+	   (weblocks::*dirty-widgets* '()))
+      (declare (special weblocks::*dirty-widgets*))
+      (setf (slot-value a 'other) b)
+      (setf d (list a b))
+      (weblocks::render-dirty-widgets)
+      (ensure-null d)
+      (ensure-same (get-output-stream-string *weblocks-output-stream*)
+		   #.(format nil "~
+{~
+\"widgets\":~
+{~
+\"a\":\"42\",~
+\"b\":\"42\"~
+},~
+\"before-load\":null,~
+\"on-load\":null~
+}")))))
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.