{"id":259,"date":"2008-10-30T15:49:11","date_gmt":"2008-10-30T13:49:11","guid":{"rendered":"http:\/\/www.thomaskeller.biz\/blog\/?p=259"},"modified":"2008-10-30T15:49:11","modified_gmt":"2008-10-30T13:49:11","slug":"global-ajax-responders-in-prototype","status":"publish","type":"post","link":"https:\/\/www.thomaskeller.biz\/blog\/2008\/10\/30\/global-ajax-responders-in-prototype\/","title":{"rendered":"Global AJAX responders in Prototype"},"content":{"rendered":"<p>I encountered a small, but ugly problem in our Symfony-driven project today: Unauthenticated AJAX requests, which f.e. may happen when the session timed out on the server, but the user hasn&#8217;t reloaded the page in the meantime, are also forwarded to the globally defined login module \/ action. This of course leaves the HTML page, which is constructed upon single HTML components, in a total mess. Ouch!<\/p>\n<p>So yeah, rendering the complete login mask HTML as partial to the client is stupid, but also relatively easy to fix:<\/p>\n<pre>\r\npublic function executeLogin($request)\r\n{\r\n    if ($request->isXmlHttpRequest())\r\n    {\r\n        \/\/ renderJSON is a custom function which json_encode's\r\n        \/\/ the argument and sets an X-JSON header on response\r\n        return $this->renderJSON(array(\"error\" =>\r\n                                    \"Your session expired\"));\r\n    }\r\n    ...\r\n}\r\n<\/pre>\n<p>This was of course only half of the fix. I still had to handle this (and other) special JSON response on the browser&#8217;s side:<\/p>\n<pre>\r\nnew Ajax.Request(\"mymodule\/myaction\", {\r\n     onSuccess: function(response, json) {\r\n         if (json.error)\r\n         {\r\n             \/\/ display the error\r\n             alert(json.error);\r\n             return;\r\n         }\r\n         \/\/ the actual callback functionality\r\n     }\r\n}\r\n<\/pre>\n<p>Uh, anyone screams <a href=\"http:\/\/en.wikipedia.org\/wiki\/Spaghetti_code\">&#8220;spaghetti code&#8221;<\/a>? Yeah, you&#8217;re right. I quickly headed for a more general implementation, also since we can&#8217;t do that for a couple of symfony-specific prototype helpers anyways, like <code>update_element_function<\/code>, whose Javascript code gets generated by Symfony dynamically. So how can this be generalized?<\/p>\n<h4>Ajax.Responders to the rescue<\/h4>\n<p>Actually, prototype already contains some kind of &#8220;global hook-in&#8221; functionality for all Ajax requests triggered by the library: <a href=\"http:\/\/www.prototypejs.org\/api\/ajax\/responders\"http:\/\/www.prototypejs.org\/api\/ajax\/responders\">Ajax.Responders<\/a>.<\/p>\n<p>While this seemed to support all common callbacks (among them onCreate, onSuccess, onFailure and onComplete), some testing showed though that f.e. the <code>onComplete<\/code> callback was always called <em>after<\/em> the specific AJAX request&#8217;s <code>onComplete<\/code> callback, so this was pretty useless for me. After all, I also wanted to <em>prevent<\/em> that the specific callback gets executed when I encountered an error&#8230;<\/p>\n<p>After diving through prototype&#8217;s code for some hours I found a solution. Particularily helpful here is that prototype signals every created Ajax request to the <code>onCreate<\/code> handler and gives the request and response object handling this request as arguments to it. Time to overwrite prototype&#8217;s responder code! Here is it:<\/p>\n<pre>\r\nAjax.Responders.register({\r\n    onCreate: function(request) {\r\n        var oldRespondToReadyState = request.respondToReadyState;\r\n        request.respondToReadyState = function(readyState) {\r\n            var state = Ajax.Request.Events[readyState];\r\n            var response = new Ajax.Response(this);\r\n            var json = response.headerJSON;\r\n            \r\n            if (state == 'Complete' && json && json.error)\r\n            {\r\n                alert(json.error);\r\n                return;\r\n            }\r\n            oldRespondToReadyState.call(response.request, \r\n                                           readyState);\r\n        }\r\n    }\r\n});\r\n<\/pre>\n<p>Another particularily useful piece of knowledge I gathered today to let this work is how <code>Function.prototype.call<\/code> and <code>Function.prototype.apply<\/code> work (both are available since Javascript 1.3).<br \/>\nBasically they allow the execution of a function in scope of the object given as first parameter (there is a nice introduction <a href=\"http:\/\/odetocode.com\/Blogs\/scott\/archive\/2007\/07\/04\/11067.aspx\">available here<\/a>).<\/p>\n<p>If you&#8217;ve ever wanted to &#8220;send an event to some object to make its listener fire&#8221; because the listener&#8217;s code depended on the fact that the <code>this<\/code> reference points to the object the event was fired upon, you should now have a viable alternative:<\/p>\n<pre>\r\nEvent.observe(myObj, 'click', myHandler);\r\n\/\/ is call-wise equivalent to\r\nmyHandler.call(myObj);\r\n<\/pre>\n<p>No need to create custom mouse events and throw them around any longer&#8230; \ud83d\ude09<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I encountered a small, but ugly problem in our Symfony-driven project today: Unauthenticated AJAX requests, which f.e. may happen when the session timed out on the server, but the user hasn&#8217;t reloaded the page in the meantime, are also forwarded to the globally defined login module \/ action. This of course leaves the HTML page, &hellip; <a href=\"https:\/\/www.thomaskeller.biz\/blog\/2008\/10\/30\/global-ajax-responders-in-prototype\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Global AJAX responders in Prototype<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3,11],"tags":[],"class_list":["post-259","post","type-post","status-publish","format-standard","hentry","category-coding","category-work"],"_links":{"self":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/259","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/comments?post=259"}],"version-history":[{"count":20,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/259\/revisions"}],"predecessor-version":[{"id":279,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/259\/revisions\/279"}],"wp:attachment":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/media?parent=259"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/categories?post=259"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/tags?post=259"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}