tag:blogger.com,1999:blog-60622761196145377782024-03-13T21:55:50.229+11:00Long Golden EarsKenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.comBlogger10125tag:blogger.com,1999:blog-6062276119614537778.post-29747121399349232512010-02-07T00:19:00.002+11:002010-02-07T00:32:55.320+11:00jQuery Namespaced Events - exclusive trigger<p>Namespaced events have been around in jQuery since 1.2, and is <a href="http://docs.jquery.com/Namespaced_Events">fairly well documented here</a>.
However, when I was looking at the jQuery (1.3.2) source code the other day, I stumbled across an interesting bit of functionality that doesn't appear to be well documented.
</p>
<p>
It appears you can also trigger non-namespaced events by providing an exclamation mark to the end of the event name (e.g. "click!"). This will then only trigger events of that type, which are not namespaced. In the source code, this is referred to as <strong>exclusive</strong>. See the trigger and handle functions.
</p>
<p><strong>For example:</strong></p>
<pre>
$(document).bind("click", function() { console.log("click"); });
$(document).bind("click.foo", function() { console.log("click.foo"); });
$(document).bind("click.foo.bar", function() { console.log("click.foo.bar"); });
$(document).triggerHandler("click");
console.log("---");
$(document).triggerHandler("click.foo");
console.log("---");
$(document).triggerHandler("click.foo.bar");
console.log("---");
$(document).triggerHandler("click!");
/*
//prints out...
click
click.foo
click.foo.bar
---
click.foo
click.foo.bar
---
click.foo.bar
---
click
*/
</pre>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com197tag:blogger.com,1999:blog-6062276119614537778.post-17152919760352309722008-08-09T15:21:00.004+10:002008-08-22T21:36:50.109+10:00jqMock - JavaScript Mock Framework for jQuery / jqUnit<p>It's been well over a week since I release version 1 of the <a href="http://code.google.com/p/jqmock/">jqMock library</a>, and I've finally finished the documentation including the <a href="http://jqmock.googlecode.com/svn/trunk/docs/userguide.html">jqMock user guide</a>, which is pretty comprehensive.</p>
<p>It has all the trimmings of your standard mock library, although I'm quick eager to wait for comments on what people think of the different approach to mocking, and whether this library is useful for everyday unit testing usage.</p>
<p>Please have a look at the code, do some code review, and leave some comments!</p>
<h3>Links</h3>
<ul>
<li><a href="http://code.google.com/p/jqmock/">jqMock - Google Code</a></li>
<li><a href="http://jqmock.googlecode.com/svn/trunk/docs/userguide.html">jqMock - User Guide</a></li>
</ul>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com10tag:blogger.com,1999:blog-6062276119614537778.post-81082283548336896162008-07-23T21:05:00.003+10:002008-07-23T21:16:10.500+10:00Global Eval in Rhino<p>I've been working to complete <a href="http://ejohn.org/blog/pure-javascript-html-parser/">John Resig's</a> env.js <a href="http://github.com/jeresig/env-js/tree/master">simulated browser environment in Rhino</a> so that it will run unit tests from the command line in exactly the same way as the browser. The end goal was to get behaviour in env.js to a point where there are practically no differences with a real browser, so that enterprises can incorporate running javascript unit tests into their CI environment.</p>
<p>Using the <a href="http://docs.jquery.com/QUnit">jquery QUnit test suite</a> as my benchmark, I've been trying make the tests pass. The going has been tough, with countless nuances to fix up, but I managed to pass the 1.1.4 core tests, and the 1.2.6 ajax and event tests. However, now stumped on the 1.2.6 core tests and realising that I won't ever have enough spare time to implement full CSS support, I'm going to blog about other areas which I've been successful with.</p>
<p>One change that made a big difference was the additional of a global eval. This means adding script elements will evaluate them in the global scope, and ajax loading of dynamic scripts with ajax now works as expected.</p>
<p>The trick is to use rhino's load() function, which loads javascript from a file system or URL location and evaluates it in the global scope. To load arbitrary snippets of javascript, the text is written to a temporary file on the filesystem, load() is called, then the file is deleted. Luckily, env.js has already implemented writing and deleting files!</p>
<pre class="code">
<em>
.....
// run node through execScripts whenever added to the dom
appendChild: function(node){
this._dom.appendChild( node._dom );
execScripts(node);
},
insertBefore: function(node,before){
this._dom.insertBefore( node._dom, before ? before._dom : before );
execScripts(node);
},
.....
function execScripts(node) {
if ( node.nodeName == "SCRIPT" ) {
if ( !node.getAttribute("src") ) {
globalEval ( node.textContent );
} else {
var src = node.getAttribute("src");
load(src); // you'll actually have to resolve relative URLs here
}
if (node.onload && typeof node.onload == "function") {
node.onload();
}
} else if (node.nodeType==1) {
var scripts = node.getElementsByTagName("script");
for ( var i = 0; i < scripts.length; i++ ) {
execScripts( scripts[i] );
}
}
}
var globalEvalCounter = (new Date()).getTime(); // temp file name
function globalEval(data) {
try { // write to java temp directory
var javatmpdir = java.lang.System.getProperty("java.io.tmpdir")+"";
var folder = "file:///" + javatmpdir.replace(/\\/g, "/");
var tempfile = folder + (globalEvalCounter++);
var xhrPut = new XMLHttpRequest();
xhrPut.open("PUT", tempfile, false);
xhrPut.send(data);
load(tempfile);
xhrPut = null;
var xhrDel = new XMLHttpRequest();
xhrDel.open("DELETE", tempfile, false);
xhrDel.send();
xhrDel = null;
} catch(ex) {
throw new Error("Error occurred");
}
}
</em>
</pre>
<p> This mechanism allows you to mimic browser behaviour much more closely, and load the scripts defined in the HTML file (like a real browser) rather than a separate js file to load the unit tests. Doing that also allows you to fire the document ready event at the right time. More on this in my next post.</p>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com219tag:blogger.com,1999:blog-6062276119614537778.post-57865439752904421732008-03-24T14:11:00.010+11:002008-03-24T20:11:52.483+11:00PortalDelegateServlet – Servlet Session Sharing in Liferay<p>After finishing my blog entry <a href="http://longgoldenears.blogspot.com/2008/03/liferay-session-sharing-demystified.html">Liferay Session Sharing Demystified</a>, i found a <a href="https://www.liferay.com/web/guest/community/forums/message_boards/message/416783">forum post</a> which pointed to the liferay Jira of <a href="http://support.liferay.com/browse/LEP-2297">LEP-2297</a>. Turns out Liferay 4.3.x has <span style="font-weight:bold;font-style:italic">built in support for running servlets from within the Portal context!</span> Yes... yet another wonderful but undocumented feature in Liferay.</p>
<p>In a nutshell, you configure your servlet to register itself with the built in PortalDelegatorServlet. This servlet (in the portal’s context) makes a cross-context call to your servlet, and provides your servlet with the portal session.</p>
<p>From a session sharing point of view, this behaves exactly the same as portlets using the portal session (i.e. portlets set to non-private session)</p>
<ul>
<li>full access to portal attributes</li>
<li>full access to non-private portlet attributes, regardless of WAR file</li>
<li>Shared attributes set by the servlet will be made visible to the private portlets</li>
<li>no access to the WAR session attributes (servlets and private portlets in the same war)</li>
</ul>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji-De2SZHULDHgM1IDQ5XfG6r-_JBybQy6FUSRejF-qMmIV0D1NP1yYQ-d00oE6v1HTcYlvVw-ImNaRP4cGAvSYQoY-e0SngpebzSQqUgtfW3XW2A_7MWnfoJEdRtAZfy83Re2UuNvldQV/s1600-h/PortalDelegateServlet.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEji-De2SZHULDHgM1IDQ5XfG6r-_JBybQy6FUSRejF-qMmIV0D1NP1yYQ-d00oE6v1HTcYlvVw-ImNaRP4cGAvSYQoY-e0SngpebzSQqUgtfW3XW2A_7MWnfoJEdRtAZfy83Re2UuNvldQV/s400/PortalDelegateServlet.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5181157912649571490" /></a>
<h3>Configuration Example</h3>
<p>The example configuration in the Jira is based on the code that was submitted, so here is an example based on the Liferay’s distribution. In your servlet’s web.xml file, configure the following servlet.</p>
<pre class="code"><em><servlet>
<servlet-name>springdemo</servlet-name>
<servlet-class>
com.liferay.portal.kernel.servlet.PortalDelegateServlet
</servlet-class>
<init-param>
<param-name>servlet-class</param-name>
<param-value>
org.springframework.web.servlet.DispatcherServlet
</param-value>
</init-param>
<init-param>
<param-name>sub-context</param-name>
<param-value>downloads</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
</em></pre>
<p>This example registers your servlet <b>example.FooServlet</b> under the key <b>foo.do</b>. (If a sub-context is not provided, the servlet-name will be used as the key instead.) When a request is made to <b>/delegate/foo.do</b> , the PortalDelegateServlet will process the URL, lookup the key <b>foo.do</b>, find your servlet, and make a cross context call to your servlet to run the service method.
If you don’t like the <b>/delegate</b> servlet-mapping, you can change this in the portal’s ROOT.war web.xml.</p>
<h3>Limitation</h3>
<p>One confusing thing is that the sub-context is used as the key to store the servlet, while only the first token of the URI is used as the key to lookup the servlet. Which means if you try to set the sub-context to <b>myapp/foo.do</b> then navigate to the URI of <b>/delegate/myapp/foo.do</b> , it won’t work, because the PortalDelegateServlet will attempt to lookup the map using the value <b>myapp</b>, and find nothing. <b><i>Bottom line: the sub-context must be only single level deep.</i></b> </p>
<h3>Using Spring DispatcherServlet</h3>
<p>If you use Spring as your front-controller, you can configure more sophisticated URLs. The DispatcherServlet can be configured for use with the PortalDelegateServlet as follows:</p>
<pre class="code"><em><servlet>
<servlet-name>springdemo</servlet-name>
<servlet-class>
com.liferay.portal.kernel.servlet.PortalDelegateServlet
</servlet-class>
<init-param>
<param-name>servlet-class</param-name>
<param-value>
org.springframework.web.servlet.DispatcherServlet
</param-value>
</init-param>
<init-param>
<param-name>sub-context</param-name>
<param-value>downloads</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
</em></pre>
<p>Then in the springdemo-servlet.xml, you will have something like this, to configure a mapping to the URL of <b>/delegate/downloads/getdocument.do</b></p>
<pre class="code"><em><bean id="myDownloadController" class="example.SpringServletController" />
<bean id="urlMapping"
class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<props>
<prop key="/downloads/getdocument.do">myDownloadController</prop>
</props>
</property>
</bean>
</em></pre>
<h3>Caveats</h3>
<p>You will need to be careful if you are using third party libraries like Spring. These libraries don’t expect you to be invoking a cross context call, so anything can happen!</p>
<p>For example, when wiring up the Spring DispatcherServlet, writing directly to the response output was fine, but returning a ModelAndView didn’t work. Further investigation revealed that InternalResourceViewResolver was looking in ROOT.war to resolve the location of the view. Which means if the prefix was /WEB-INF/jsp/ , you would have to put this inside /ROOT.war/WEB-INF/jsp , which is clearly not workable.</p>
<p>However, servlets are usually deployed in the portal is to enable some sort of file download, which requires you to write directly to the response output stream anyway, and for this use case the PortalDelegateServlet is the perfect tool.</p>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com98tag:blogger.com,1999:blog-6062276119614537778.post-40444448231168892322008-03-23T12:23:00.013+11:002008-03-24T20:15:26.239+11:00Liferay Session Sharing Demystified<p>Liferay’s session sharing mechanism has always been a bit of a mystery. As with most things Liferay, documentation is minimal, and leaves a lot of questions to be answered. I’d like to share with you what I’ve learnt after testing a bunch of scenarios, and hopefully this will be good reference material for all of you developing portlets on Liferay.</p>
<p>My scenarios focus on how Liferay’s sessions behave for different combinations of the <b><i>private-session-attributes</i></b> settings; between the portal, portlets (in the same WAR, and across different WARs) and the servlets in these WARs. This also means that only APPLICATION_SCOPE attributes are relevant to the discussion.</p>
<p>Testing was performed on Liferay 4.3.6, and consisted of basic JSR-168 portlets , and basic Servlet Spec servlets. To emulate code running in the portal scope, a simple servlet Filter was configured on the Portal application.</p>
<h3>Private Session True</h3>
<p>This is the default setting, in which each WAR has its own session. The session within each WAR is private, and nothing is shared between individual WARs. Portlets and Servlet within the same WAR file will be able to share the session, because they are in the same context. So far, this conforms to the Servlet spec.</p>
<p>Liferay provides an additional functionality under this setting. As the <a href="http://wiki.liferay.com/index.php/Liferay_FAQ#How_do_we_use_the_session_to_share_attributes_between_portlets_deployed_as_individual_WARs.3F">official FAQ</a> states, it also allows shared (namespaced) attributes set by the portal to be visible from portlets. Any session attribute with the configured prefix (e.g. LIFERAY_SHARED_) will be copied over and be visible by the portlets, hence granting private portlets read-access to session attributes set by the portal. This is illustrated in diagram 1.</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil2bdzcZeGCDbOXObN4dM8n9Cy-eKN78npJYqqN3-vOC4F80Ra-8qZVkQ4KbbH84IANky8bqXkFfL4tSweOlu8XmyUvcZFQDGiekY7iGVRpIpK1-5Hw_SAot2dGKCAYiqQnw5GksZM-HId/s1600-h/1_privatetrue.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil2bdzcZeGCDbOXObN4dM8n9Cy-eKN78npJYqqN3-vOC4F80Ra-8qZVkQ4KbbH84IANky8bqXkFfL4tSweOlu8XmyUvcZFQDGiekY7iGVRpIpK1-5Hw_SAot2dGKCAYiqQnw5GksZM-HId/s400/1_privatetrue.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180742447578127410" /></a>
<span class="imageCaption">Diagram 1</span>
<h3>Private Session False</h3>
<p>All portlets under this setting will share the same session, and also share this same session with the portal. This is the easiest way for portlets in individual WAR files to communicate with each other.</p>
<p>However, the downside to this is that servlets in these WAR files will not be able to communicate with the portlets at all. (This is a question often raised in the forums, and one <a href="http://forum.springframework.org/showthread.php?t=47295&page=2">I struggled with for a while to figure out</a>). The most convenient way to think of this is that portlets with this setting use the portal session, and have no access to the WAR session. This is illustrated in diagram 2.</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh87Wl25uQr_XoxYrYCOk55at-sDfW4V7AHvzbnPSs9YfJEl2Yh19tuIHjniz7moyAkRfU0sWHI0CsbBKXq_94echdRBI0BUbht6eeVnqJLeNqbX4-OgMstz_kJgYxQUtpKCvDfHvXxyNLF/s1600-h/2_privatefalse.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh87Wl25uQr_XoxYrYCOk55at-sDfW4V7AHvzbnPSs9YfJEl2Yh19tuIHjniz7moyAkRfU0sWHI0CsbBKXq_94echdRBI0BUbht6eeVnqJLeNqbX4-OgMstz_kJgYxQUtpKCvDfHvXxyNLF/s400/2_privatefalse.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180742456168062018" /></a>
<span class="imageCaption">Diagram 2</span>
<h3>Mixed Scenarios</h3>
<p>Diagram 3 depicts the interaction between a private and non-private portlet in individual WAR files. Portlet A, being non-private, has full communication with the portal, but cannot talk to Servlet A. Portlet B, being private, has full communication with Servlet B, but only has read-only access to Portal shared attributes. </p>
<p>You will notice arrow from Portlet A to Portlet B is dotted because this communication happening indirectly through the portal session. Portlet A is using the portal session, so any shared attributes that it sets will also be copied over to Portlet B, and can be read.</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcEumCV_XFHvzYZObXV8bhtuAsF6sqAB2fjeDfj3KV1983KllB93r0UzEWEOLwbPXOEhNqA7AtRFtZlvj8IIDd-rMUkJhxxe3mWQjpOEJZ0Bg255KX4XZFEKE6gx6lg7zGwLYBU9_LeGQH/s1600-h/3_mixed1.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcEumCV_XFHvzYZObXV8bhtuAsF6sqAB2fjeDfj3KV1983KllB93r0UzEWEOLwbPXOEhNqA7AtRFtZlvj8IIDd-rMUkJhxxe3mWQjpOEJZ0Bg255KX4XZFEKE6gx6lg7zGwLYBU9_LeGQH/s400/3_mixed1.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180742456168062034" /></a>
<span class="imageCaption">Diagram 3</span>
<p>Diagram 4 expands on diagram 3, and adds another private portlet to WAR B. Both of the portlets in B are private, meaning they are using the WAR session, so they can freely communicate with each other and the servlet. Same as the previous example, they will be able to read shared attributes set by the portal, or by any non-private portlet like Portlet A.</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMOXQdvs-34Nh3n59gtSAP6ZCu15mJ5AK8sRkaN14bP82IOqnPatN4llfbWEBZK1PFZRkPIdGAcHWa1SZ347-m8rGJO-Qk17f86f-SWRENcklMmpdg5iqQ17KrXnkpAdlC4Pw5rQEQ4LTO/s1600-h/4_mixed2.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMOXQdvs-34Nh3n59gtSAP6ZCu15mJ5AK8sRkaN14bP82IOqnPatN4llfbWEBZK1PFZRkPIdGAcHWa1SZ347-m8rGJO-Qk17f86f-SWRENcklMmpdg5iqQ17KrXnkpAdlC4Pw5rQEQ4LTO/s400/4_mixed2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180742460463029346" /></a>
<span class="imageCaption">Diagram 4</span>
<p>Diagram 5 is a bit more interesting. Here, Portlet B has been changed to non-private, meaning it will use the portal session. That is why it can now freely communicate with the portal and Portlet A. However, just like anything else using the Portal session, it cannot read the WAR session of B anymore, even though it is in the same WAR! While this may seem a little counter-intuitive, it is consistent with the behaviour we have seen so far.</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvru787755ae7ZRtYOF6UMhi6v_QTKstUyxSPHlag1oByA6GSUE8gGGm7SDD5XA63d_3ZO1zJl7GA_B90yaAccv-7gODUOyxypywAGJPuUaIuh9ke16es49huQbJttPIKWBBv2J3sXP2aN/s1600-h/5_mixed3.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvru787755ae7ZRtYOF6UMhi6v_QTKstUyxSPHlag1oByA6GSUE8gGGm7SDD5XA63d_3ZO1zJl7GA_B90yaAccv-7gODUOyxypywAGJPuUaIuh9ke16es49huQbJttPIKWBBv2J3sXP2aN/s400/5_mixed3.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180742460463029362" /></a>
<span class="imageCaption">Diagram 5</span>
<h3>Copying shared attributes to a Servlet</h3>
<p style="font-weight:bold; font-style:italic;color:#993333">Update: <a href="http://longgoldenears.blogspot.com/2008/03/portaldelegateservlet-servlet-session.html">Liferay has a built in mechanism for calling servlets from the portal context</a>, giving them access to the portal session.</p>
<p>You may have noticed so far that the servlets cannot directly read the shared attributes of the portal session. But you can get around this by using a private portlet as a proxy. Diagram 6 shows the flow of how a shared attribute x, set by non-private Portlet A, will be stored in the portal session and subsequently copied to private Portlet B. Portlet B, having write access to WAR session B, can set another attribute y to the same value as x, and y can be read by the servlet.</p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYH8OgkAurmCJvjR5KTtWblifpIEMhCHTApL0F5dUCDzzGLtjjFkWs1QBIe7-E0ZWXdWPnqWcRNWHlPYHsPBwhQQF1mvM-LKWCB6CLEr5mKockejT2QaD59YpztGIcFLq-fuukKL7cavCF/s1600-h/6_copying.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgYH8OgkAurmCJvjR5KTtWblifpIEMhCHTApL0F5dUCDzzGLtjjFkWs1QBIe7-E0ZWXdWPnqWcRNWHlPYHsPBwhQQF1mvM-LKWCB6CLEr5mKockejT2QaD59YpztGIcFLq-fuukKL7cavCF/s400/6_copying.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180746360293334146" /></a>
<span class="imageCaption">Diagram 6</span>
<h3>Overwriting shared attributes from private portlets?</h3>
<p>We know that any session attributes set by private portlets cannot be seen by the portal, or by any other non-private portlets. But what happens if the private portlet sets an attribute of the same key as the shared attribute? </p>
<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigFDmDiyHtFvbFJZstkNVpKf543-X__CDWauNpwja930NpGnqNtdrguWKyiu8Bl_IUo7WaF5FFoVyygAirVeoAqdHGRdOWwLssI7MB2kb5AawZv0Bjzq7BPvs_dUkcLsToi_wTSusYyL-6/s1600-h/7_clobber.png"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEigFDmDiyHtFvbFJZstkNVpKf543-X__CDWauNpwja930NpGnqNtdrguWKyiu8Bl_IUo7WaF5FFoVyygAirVeoAqdHGRdOWwLssI7MB2kb5AawZv0Bjzq7BPvs_dUkcLsToi_wTSusYyL-6/s400/7_clobber.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5180746364588301458" /></a>
<span class="imageCaption">Diagram 7</span>
<p>In Diagram 7, the flow is depicted more accurately by showing two sessions – the Portal session and the WAR session of B. I will walk through the numbered scenario as follows:</p>
<ol>
<li>Non-private Portlet A sets a session attribute <b><i>LIFERAY_SHARED_foo</i></b> to <b><i>“alice”</i></b> . This is stored in the portal session.</li>
<li>Private Portlet B reads the attribute <b><i>LIFERAY_SHARED_foo</i></b>. Since this is a shared attribute, it has read access, and returns the value <b><i>“apple”</i></b> .</li>
<li>Private Portlet B2 now sets the same session attribute <b><i>LIFERAY_SHARED_foo</i></b> to <b><i>“bob”</i></b> . Because it is a private portlet, the value is written to the WAR session.</li>
<li>When Portlet B tries to read the attribute again, it returns the value <b><i>“bob”</i></b>. This shows that values in the WAR session override those copied from the portal. If at this point Portlet A were to set the attribute again, Portlet B cannot see the change.</li>
<li>Portlet B2 now removes the session attribute <b><i>LIFERAY_SHARED_foo</i></b></li>
<li>When Portlet B tries to read the attribute again, it returns the value <b><i>“alice”</i></b> from the portal session.</li>
</ol>
<p>This example shows that when private portlets tries to read an attribute, it will first read the attribute from the WAR session. If it can’t be found, it will try to read from any shared attributes copied from the Portal session.</p>
<h3>Summary</h3>
<p>I am not a core Liferay developer, so I’m not sure what the underlying implementation really is. However, my observations are consistent with the following summary:</p>
<ul>
<li>Non-private portlets read and write to the Portal session.</li>
<li>Private portlets write to their own WAR session.</li>
<li>Private portlets try to read from their own WAR session first, then looks up any shared attributes copied from the Portal session.</li>
<li>Servlets only have access to the WAR session, and cannot directly access the Portal session. In order to read shared session attributes, servlets need a private portlet in the same WAR file to copy it for them. Or you can configure your servlet to use the portal session by using <a href="http://longgoldenears.blogspot.com/2008/03/portaldelegateservlet-servlet-session.html">Liferay's PortalDelegateServlet mechanism</a>.</li>
</ul>
<p>I hope this has helped you in your understanding of Liferay’s session sharing mechanism. If you are a core Liferay developer, please let me know if I have made any mistakes, or if you have anything else to add. I’d be keen to know if this is still relevant to Liferay 4.4.x</p>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com188tag:blogger.com,1999:blog-6062276119614537778.post-7580442471539604862007-12-28T23:28:00.001+11:002008-07-18T20:56:48.143+10:00Portlet SimpleFormController Cheat Sheet<p>It's holiday season so I've finally found a bit of time to create a version of the SimpleFormController cheat sheet for the portlet MVC. Once again, remember that this cheat sheet should be used in conjunction with the API and source code. It is not meant to be a comprehensive translation of the source code. Rather, it is meant to highlight important areas where your own implementation should hook in to the standard workflow.</p>
<p>When compared to the servlet, you can see that the portlet workflow differs mainly in the seperation of the action phase and render phase, as per the JSR-168 spec. Some main points of comparison to note:</p>
<ul>
<li>showNewForm() and showForm() will be called during the render phase (depicted on the LHS of the diagram). </li>
<li>The 3 main stages of submission (backing object phase, bind and validate phase, process form submission phase) are found in the action phase (depicted on the RHS of the diagram).</li>
<li>In the servlet version, we often need to bind the original form on sucessView, and we did this by calling showForm() during onSubmit().
The same thing is achieved in the portlet version by calling showForm() during onSubmitRender()</li>
</ul>
<p>I've tweaked the colors slightly, so hopefully they will turn out a bit better on grayscale printers. The servlet version of the cheat sheet has been updated to keep the two cheat sheets consistent.</p>
<p>Hope you will will find this useful. Please leave comments and let me know if anything is wrong, or if there are any suggestions. Happy new year!</p>
<a class="adobeLink" href="http://users.tpg.com.au/fuzziman/blog/PortletSimpleFormControllerCheatSheet_1.0.pdf">
Portlet SimpleFormController Cheat Sheet
</a>
<a class="adobeLink" href="http://users.tpg.com.au/fuzziman/blog/SimpleFormControllerCheatSheet_1.2.pdf">
Servlet SimpleFormController Cheat Sheet
</a>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com139tag:blogger.com,1999:blog-6062276119614537778.post-10804170158041531702007-09-01T21:32:00.000+10:002007-09-01T21:52:54.590+10:00Triple equals in JavaScript<p>I think I'm a pretty decent javascript programmer, so it's always refreshing to learn something new in a language I'm so familiar with! I recently had some time to watch those great lectures from Doug Crockford at <a href="http://developer.yahoo.com/yui/theater/">YUI Theatre</a>, and noticed lots of <span style="font-weight:bold;">===</span> in the code. </p>
<p>That's right, it's not a typo, but 3 equal signs, and it means <span style="font-weight:bold;font-style:italic;">equality without type coersion</span>. In other words, if using the triple equals, the values must be equal in type as well.</p>
e.g.
<pre class="code"><em>0==false // true
0===false // false, because they are of a different type
1=="1" // true, auto type coersion
1==="1" // false, because they are of a different type
</em></pre>
<p>Another handy tool in my javascript bag of tricks, as recommended by Mr father of javascript himself!</p>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com249tag:blogger.com,1999:blog-6062276119614537778.post-92187674774937717812007-07-04T18:42:00.001+10:002008-07-18T20:54:04.226+10:00SimpleFormController Cheat Sheet<p>I felt I didn't have a good understanding of Spring MVC until I understood the lifecycle of the controller. Not being able to find any good resources that showed the workflow step by step, I created this flowchart of the SimpleFormController. </p>
<p>In a single page cheat sheet format, I've found it handy to keep it on my desk. For the beginner, it should be used in conjunction with the API, and nothing beats firing up a debugger and stepping through the code yourself. Once you understand the SimpleFormController, you can easily understand the WizardFormController or create your own custom controller.</p>
<p>I hope someone will find this useful. Please leave comments and let me know if anything is wrong, or if there are any suggestions. Cheers!</p>
<a class="adobeLink" href="http://users.tpg.com.au/fuzziman/blog/SimpleFormControllerCheatSheet_1.2.pdf">
SimpleFormController Cheat Sheet
</a>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com37tag:blogger.com,1999:blog-6062276119614537778.post-48966318234143733022007-07-03T18:08:00.000+10:002007-07-06T16:35:10.689+10:00Fluid Horizontal Menu - Using UL, CSS (and a table)<p>In the <a href="http://www.alistapart.com/articles/taminglists/">A List Apart article: Taming Lists</a> there is a very simple example of using UL to markup a horizontal list. Making this into a fluid full width menu proved much more difficult than I thought, and I have provided the implementation here.</p>
<p>The broad requirements of this are:</p>
<ul>
<li>There is a color for the menu, and another color for the current item. The menu has a bottom border the same color as the current item.</li>
<li>The menu must not wrap when browser is resized, it must always be a single horizontal row</li>
<li>When browser width is smaller than menu, scrolling the viewport across will still display the menu bottom border</li>
<li>When browser width is larger than the menu, the menu expands to 100% width</li>
<li>The items in the menu are dynamically generated on the server (so the number of items may vary, as can the total possible width)</li>
</ul>Show below, is the screenshot of the final product.<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDwyyrfk439qxLlzGbLmvW40-hqDKEpZaJb5GSMJCVJoMEyv8GZhYd8aWhHpeC6Z1jhAtKCTfAfc5r6IsGwCZSGItA8igwAC23Ta3geGiWum99hC-9OGwOQ9pJm3TEKnOMlRXAaeKjKnDY/s1600-h/fluidmenu.png"><img style="cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDwyyrfk439qxLlzGbLmvW40-hqDKEpZaJb5GSMJCVJoMEyv8GZhYd8aWhHpeC6Z1jhAtKCTfAfc5r6IsGwCZSGItA8igwAC23Ta3geGiWum99hC-9OGwOQ9pJm3TEKnOMlRXAaeKjKnDY/s400/fluidmenu.png" alt="" id="BLOGGER_PHOTO_ID_5082880010335092450" border="0" /></a>
<p>The final code to produce this menu is as follows:</p>
<pre class="code"><em><style type="text/css">
li, ul {
padding: 0;
margin:0;
}
table.menu {
background-color: #CCC;
border-bottom: 5px solid #8C78C5;
width: 100%;
padding: 0;
border-collapse:collapse;
}
table.menu td, table.menu tr {
border: 0; padding: 0;
}
table.menu ul {
white-space: nowrap;
padding: 4px 0px;
}
table.menu li{
display: inline;
list-style: none;
}
table.menu a{
background-color: #CCC;
padding: 4px 10px;
color: black;
text-decoration: none;
border-left: 1px solid white;
}
table.menu a:hover {
text-decoration: underline;
}
table.menu a.first {
border-left: none;
}
table.menu a.current{
color: white;
background-color: #8C78C5;
}
</style>
<!--[if IE]>
<style type="text/css">
table.menu ul{
width:110%;
}
</style>
<![endif]-->
<table class="menu"><tr><td><!--
--><ul><!--
--><li><a href="#" class="first">Spring MVC framework</a></li><!--
--><li><a href="#">Struts MVC</a></li><!--
--><li><a href="#" class="current">Tapestry</a></li><!--
--><li><a href="#">JSF</a></li><!--
--><li><a href="#">Java Server Pages</a></li><!--
--></ul><!--
--></td></tr></table>
</em></pre>
<p>To summarise the main points of the implementation:</p>
<ul>
<li>The LI are inline, and the UL has white-space:nowrap so that the items are in a single row</li>
<li>The background-color is defined in the anchor so that the entire "tab" is clickable</li>
<li>To increase the height of each "tab", add padding to the anchor, and also to the UL because the anchors are inline elements</li>
<li>We need the container to expand to accomodate its content regardless of browser viewport size, so that the bottom border stretches with the content. A table is used because IE doesn't suppport display:table* attributes</li>
<li>Whitespace is commented out because for some reason compliant browsers render a gap</li>
<li>An IE only rule is provided to solve a problem which appears to be collapsing related.</li>
</ul>
<p>For the full explaination of this menu, see my article <a href="http://home.exetel.com.au/cweatures/dev/fluidmenu.html">Fluid Horizontal Menu - Using Unordered List, XHTML, CSS (and a table)</a></p>
<p>This menu has been tested on IE6, IE7, Firefox 2, Opera 9, and Safari 3beta</p>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com263tag:blogger.com,1999:blog-6062276119614537778.post-29388081439298019832006-12-29T17:52:00.000+11:002006-12-29T18:00:42.659+11:00In the beginning...<a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh84Jw9qw3A4R9uIjREbimYRfEoMEY3r16ZMJQBh8iWimwxBahDowkMhA0SDyBCetWBRKFxz4BwQOm-Rp61mfSVVYSupFHrX_bbj3mwOfISmz22nJFrdTJanKCdPW8v6hBSJUEuucUqzJyW/s1600-h/sleepychase.JPG"><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh84Jw9qw3A4R9uIjREbimYRfEoMEY3r16ZMJQBh8iWimwxBahDowkMhA0SDyBCetWBRKFxz4BwQOm-Rp61mfSVVYSupFHrX_bbj3mwOfISmz22nJFrdTJanKCdPW8v6hBSJUEuucUqzJyW/s320/sleepychase.JPG" alt="" id="BLOGGER_PHOTO_ID_5013839964726936674" border="0" /></a>
Welcome to my blog post!
There is nothing to say, so I'll leave you with a picture of my loved pup, <span style="font-weight: bold;">Chase</span>Kenneth Kohttp://www.blogger.com/profile/14492721219364354508noreply@blogger.com4