<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-492842211449076546</id><updated>2012-01-02T12:36:57.918+06:00</updated><category term='pagination'/><category term='bilingual reading'/><category term='Atlassian'/><category term='form validation'/><category term='persistence'/><category term='servlets'/><category term='languages'/><category term='Hibernate'/><category term='WebWork'/><category term='learning'/><category term='Confluence'/><category term='Ajax'/><category term='Breadcrumbs'/><title type='text'>weblitera</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://weblitera.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://weblitera.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>dima</name><uri>http://www.blogger.com/profile/10231325970887124326</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>6</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-492842211449076546.post-3354956667834154987</id><published>2009-05-27T07:26:00.006+06:00</published><updated>2009-12-11T14:14:49.752+05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Breadcrumbs'/><category scheme='http://www.blogger.com/atom/ns#' term='Confluence'/><category scheme='http://www.blogger.com/atom/ns#' term='Atlassian'/><title type='text'>Custom breadcrumbs in Confluence plugin</title><content type='html'>5th part in Confluence plugin development series..&lt;br /&gt;Often pages in plugin have hierarhical structure, so if you for example have 2 pages - &lt;code&gt;Departments&lt;/code&gt; and &lt;code&gt;Edit Department&lt;/code&gt;, it's preferred that your visotors may navigate fast within these pages. The goal may be achived with a help of top navigation bar, but unfortunatelly confluence only provides backlink to &lt;code&gt;Dashboard&lt;/code&gt;:&lt;br /&gt;&lt;img style="width: 186px; height: 24px;" src="http://4.bp.blogspot.com/_10bysvPeeRI/SyHNkLEA_RI/AAAAAAAAAEA/ZN7TvgcwfD8/s320/pic1.png" alt="" id="BLOGGER_PHOTO_ID_5413834248404663570" border="0" /&gt;&lt;br /&gt;But what you really prefer is:&lt;br /&gt;&lt;img style="width: 273px; height: 23px;" src="http://2.bp.blogspot.com/_10bysvPeeRI/SyHOOJvux_I/AAAAAAAAAEI/YCHqFfykyvU/s320/pic2.png" border="0" alt=""id="BLOGGER_PHOTO_ID_5413834969605654514" /&gt;&lt;br /&gt;The idea is to create your own &lt;code&gt;breadcrumbs&lt;/code&gt; content tag.&lt;br /&gt;&lt;br /&gt;&lt;li&gt;create new file &lt;code&gt;breadcrumbs.vm&lt;/code&gt; in your &lt;code&gt;departments&lt;/code&gt; directory:&lt;pre&gt;#requireResource(&amp;quot;confluence.web.resources:yui-core&amp;quot;)&lt;br /&gt;#requireResource(&amp;quot;confluence.web.resources:ajs&amp;quot;)&lt;br /&gt;#requireResource(&amp;quot;confluence.web.resources:breadcrumbs&amp;quot;)&lt;br /&gt;&amp;lt;content tag=&amp;quot;breadcrumbs&amp;quot;&amp;gt;&lt;br /&gt; &amp;lt;ol id=&amp;quot;breadcrumbs&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;span&amp;gt;#dashboardlink ()&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;span&amp;gt;&amp;lt;a href=&amp;quot;$req.contextPath/&lt;br /&gt;   admin/departments/observe.action &amp;quot;&amp;gt;Departments&amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;span&amp;gt;Edit Department&amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt; &amp;lt;/ol&amp;gt;&lt;br /&gt;&amp;lt;/content&amp;gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;include this file in the end of your page:&lt;pre&gt;  ...&lt;br /&gt;  #parse&lt;br /&gt;   (&amp;quot;/templates/userinfo/departments/breadcrumbs.vm&amp;quot;)&lt;br /&gt; &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;/pre&gt;&lt;/li&gt;More extended example may dynamically load action names and URLs. The following code uses i18n for action names in breadcrumbs:&lt;li&gt;&lt;code&gt;breadcrumbs.vm&lt;/code&gt; looks like this:&lt;pre&gt;#requireResource(&amp;quot;confluence.web.resources:yui-core&amp;quot;)&lt;br /&gt;#requireResource(&amp;quot;confluence.web.resources:ajs&amp;quot;)&lt;br /&gt;#requireResource(&amp;quot;confluence.web.resources:breadcrumbs&amp;quot;)&lt;br /&gt;&amp;lt;content tag=&amp;quot;breadcrumbs&amp;quot;&amp;gt;&lt;br /&gt; &amp;lt;ol id=&amp;quot;breadcrumbs&amp;quot;&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;span&amp;gt;#dashboardlink () &amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  #set ($parentClass = $action.getParentClass())&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;span&amp;gt;&amp;lt;a href=&amp;quot;$req.contextPath/admin/&lt;br /&gt;   departments/observe.action&amp;quot;&amp;gt;&lt;br /&gt;   $action.getActionName($parentClass.getName())&amp;lt;/a&amp;gt;&lt;br /&gt;  &amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt;  &amp;lt;li&amp;gt;&amp;lt;span&amp;gt;&lt;br /&gt;  $action.getActionName($action.getClass().getName())&lt;br /&gt;  &amp;lt;/span&amp;gt;&amp;lt;/li&amp;gt;&lt;br /&gt; &amp;lt;/ol&amp;gt;&lt;br /&gt;&amp;lt;/content&amp;gt;&lt;/pre&gt;&lt;/li&gt;&lt;li&gt;&lt;code&gt;breadcrumbs&lt;/code&gt; tag uses action's static function to get parent:&lt;pre&gt;public class EditDepartment &lt;br /&gt;  extends ConfluenceActionSupport {&lt;br /&gt; ...&lt;br /&gt; public static Class&amp;lt;?&amp;gt; getParentClass() {&lt;br /&gt;  return ViewDepartments.class;&lt;br /&gt; }&lt;br /&gt; ...&lt;br /&gt;}&lt;/pre&gt;&lt;/li&gt;After we know parent's class name it's easy to get it's label:&lt;pre&gt;$action.getActionName($action.getClass().getName())&lt;/pre&gt;&lt;br /&gt;Of course class names must be defined in i18n properties:&lt;pre&gt;userinfo.departments.action.ViewDepartments.action.name=&lt;br /&gt; Departments&lt;br /&gt;userinfo.departments.action.EditDepartment.action.name=&lt;br /&gt; Edit department&lt;/pre&gt;&lt;br /&gt;There are lots of things you can do here, make universal code for any page in your plugin, multi level navigations etc!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/492842211449076546-3354956667834154987?l=weblitera.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://weblitera.blogspot.com/feeds/3354956667834154987/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://weblitera.blogspot.com/2009/05/custom-breadcrumbs-in-confluence-plugin.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/3354956667834154987'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/3354956667834154987'/><link rel='alternate' type='text/html' href='http://weblitera.blogspot.com/2009/05/custom-breadcrumbs-in-confluence-plugin.html' title='Custom breadcrumbs in Confluence plugin'/><author><name>dima</name><uri>http://www.blogger.com/profile/10231325970887124326</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_10bysvPeeRI/SyHNkLEA_RI/AAAAAAAAAEA/ZN7TvgcwfD8/s72-c/pic1.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-492842211449076546.post-6337175440721166798</id><published>2009-05-13T14:58:00.007+06:00</published><updated>2009-05-15T10:01:11.600+06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Confluence'/><category scheme='http://www.blogger.com/atom/ns#' term='Ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='WebWork'/><category scheme='http://www.blogger.com/atom/ns#' term='Atlassian'/><category scheme='http://www.blogger.com/atom/ns#' term='form validation'/><title type='text'>Ajax form validation in Confluence plugin using a JSON request</title><content type='html'>4th part in Confluence plugin development series..&lt;br /&gt;Today I describe how to make Ajax requests to XWork actions inside a &lt;a href="http://confluence.atlassian.com/display/DOC/Confluence+Development+Hub"&gt;Confluence plugin&lt;/a&gt;. A good example is server-side form validation. User fills a form, clicks &lt;code&gt;Submit&lt;/code&gt;, then we validate the form in background and if there are no errors we allow submitting the form, otherwise show field errors.&lt;br /&gt;&lt;br /&gt;Suppose we have a simple user info form with input fields like name, department, email etc:&lt;pre&gt;&amp;lt;form id=&amp;quot;saveform&amp;quot; action=&amp;quot;doUpdate.action&amp;quot; method=&amp;quot;post&amp;quot;&amp;gt;&lt;br /&gt;  #tag( TextField &amp;quot;name='user.name'&amp;quot; &amp;quot;size='50'&amp;quot; )&lt;br /&gt;  #tag( Select &amp;quot;name='user.department'&amp;quot; &lt;br /&gt;    &amp;quot;list=departmentsDao.all&amp;quot; &lt;br /&gt;    &amp;quot;listKey=name&amp;quot; &amp;quot;listValue=name&amp;quot; &lt;br /&gt;    &amp;quot;emptyOption='true'&amp;quot; )&lt;br /&gt;  #tag( TextField "name='user.email'" "size='50'" )&lt;br /&gt;...&lt;br /&gt;  #tag( Submit "id=userformsubmit" &lt;br /&gt;    "name='save'" "value='dot.button.save'" )&lt;br /&gt;  #tag( Submit "name='cancel'" "value='dot.button.cancel'" )&lt;br /&gt;&amp;lt;/form&amp;gt;&lt;/pre&gt;One way to make form validation is to write it in javascript, but what if you already have it implemented in action? Write everything again? Maybe it's better to use existing server side code so you can switch on/off Ajax at any time and you'll not have the same logic implemented twice. &lt;br /&gt;Here are the required steps:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;edit atlassian-plugin.xml and create a package for you application with validation enabled, declare an action &lt;code&gt;doUpdate&lt;/code&gt; for submitting a form and &lt;code&gt;editValidate&lt;/code&gt; for Ajax validation&lt;/li&gt;&lt;pre&gt;&amp;lt;package name=&amp;quot;userinfo&amp;quot; extends=&amp;quot;default&amp;quot; &lt;br /&gt;  namespace=&amp;quot;/dot/users&amp;quot;&amp;gt; &lt;br /&gt;  &amp;lt;default-interceptor-ref name=&amp;quot;validatingStack&amp;quot;/&amp;gt;&lt;br /&gt;...&lt;br /&gt;  &amp;lt;action name=&amp;quot;doUpdate&amp;quot; &lt;br /&gt;   class=&amp;quot;dot.userinfo.users.action.EditUserInfo&amp;quot; &lt;br /&gt;   method=&amp;quot;save&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;external-ref name=&amp;quot;dotUsersDao&amp;quot;&amp;gt;&lt;br /&gt;      usersDao&amp;lt;/external-ref&amp;gt;&lt;br /&gt;    &amp;lt;external-ref name=&amp;quot;dotDepartmentsDao&amp;quot;&amp;gt;&lt;br /&gt;      departmentsDao&amp;lt;/external-ref&amp;gt;&lt;br /&gt;    &amp;lt;external-ref name=&amp;quot;dotCitiesDao&amp;quot;&amp;gt;&lt;br /&gt;      citiesDao&amp;lt;/external-ref&amp;gt;&lt;br /&gt;    &amp;lt;result name=&amp;quot;input&amp;quot; type=&amp;quot;velocity&amp;quot;&amp;gt;&lt;br /&gt;      /templates/dot/userinfo/users/edituser.vm&amp;lt;/result&amp;gt; &lt;br /&gt;    &amp;lt;result name=&amp;quot;success&amp;quot; type=&amp;quot;redirect&amp;quot;&amp;gt;&lt;br /&gt;      /dot/users/usercard.action?id=${user.id}&amp;lt;/result&amp;gt; &lt;br /&gt;    &amp;lt;result name=&amp;quot;cancel&amp;quot; type=&amp;quot;redirect&amp;quot;&amp;gt;&lt;br /&gt;      /dot/users/observe.action&amp;lt;/result&amp;gt; &lt;br /&gt;  &amp;lt;/action&amp;gt;&lt;br /&gt;...&lt;br /&gt;  &amp;lt;action name=&amp;quot;editValidate&amp;quot; &lt;br /&gt;    class=&amp;quot;dot.userinfo.users.action.EditUserInfo&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;result name=&amp;quot;input&amp;quot; type=&amp;quot;json&amp;quot; /&amp;gt; &lt;br /&gt;    &amp;lt;result name=&amp;quot;success&amp;quot; type=&amp;quot;json&amp;quot; /&amp;gt; &lt;br /&gt;  &amp;lt;/action&amp;gt;&lt;br /&gt;...&lt;br /&gt;&amp;lt;/package&amp;gt;&lt;/pre&gt;I turn on &lt;code&gt;validatingStack&lt;/code&gt; for the whole package so I could use &lt;a href="http://www.opensymphony.com/webwork/wikidocs/Validation.html"&gt;WebWork validation&lt;/a&gt; everywhere I need it. Don't forget to turn off validation for actions where you don't need it, use &lt;code&gt;defaultStack&lt;/code&gt; for that&lt;pre&gt;&amp;lt;interceptor-ref name=&amp;quot;defaultStack&amp;quot;/&amp;gt;&lt;/pre&gt;Action &lt;code&gt;editValidate&lt;/code&gt; will be called in Ajax from javascript. In my experience, both &lt;code&gt;input&lt;/code&gt; and &lt;code&gt;success&lt;/code&gt; return types are required. &lt;code&gt;input&lt;/code&gt; is returned in case of any field errors, &lt;code&gt;success&lt;/code&gt; when everything is ok. Actually there is no need to return &lt;code&gt;success&lt;/code&gt; to javascript, but instead I could save the form and redirect to another form. I'll try this in next post.&lt;br /&gt;&lt;br /&gt;&lt;li&gt;create EditUserInfo action and implement JSONAction interface&lt;/li&gt;&lt;pre&gt;public class EditUserInfo &lt;br /&gt;  extends ConfluenceActionSupport &lt;br /&gt;  implements &lt;b&gt;JSONAction&lt;/b&gt; {&lt;br /&gt;  ...&lt;br /&gt;  public String getJSONString() {&lt;br /&gt;    try {&lt;br /&gt;      if (hasFieldErrors()) {&lt;br /&gt;        StringBuffer sb = new StringBuffer();&lt;br /&gt;        sb.append(&amp;quot;[&amp;quot;);&lt;br /&gt;        Map fieldErrors = getFieldErrors();&lt;br /&gt;        for (Object field : fieldErrors.keySet()) {&lt;br /&gt;          List&amp;lt;Object&amp;gt; msg = &lt;br /&gt;            (List&amp;lt;Object&amp;gt;) fieldErrors.get(field);&lt;br /&gt;          if (field != null &amp;amp;&amp;amp; msg != null &lt;br /&gt;              &amp;amp;&amp;amp; msg.get(0) != null) {&lt;br /&gt;            sb.append(&amp;quot;{field:'&amp;quot;+field+&amp;quot;', &lt;br /&gt;              error:'&amp;quot;+msg.get(0)+&amp;quot;'},&amp;quot;);&lt;br /&gt;          }&lt;br /&gt;        }&lt;br /&gt;        if (sb.length() &amp;gt; 1) {&lt;br /&gt;          sb.deleteCharAt(sb.length()-1);&lt;br /&gt;        }&lt;br /&gt;        sb.append(&amp;quot;]&amp;quot;);&lt;br /&gt;        return sb.toString();&lt;br /&gt;      }&lt;br /&gt;    } catch (Exception e) {&lt;br /&gt;      // handle exception&lt;br /&gt;    }&lt;br /&gt;    return &amp;quot;&amp;quot;;&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;By the time &lt;code&gt;getJSONString()&lt;/code&gt; method is called validation was already done by WebWork so we can immediatelly process the errors. Here I generate a Javascript array of all field errors which I want to show next to input fieds.&lt;br /&gt;&lt;br /&gt;&lt;li&gt;write &lt;code&gt;EditUserInfo-validation.xml&lt;/code&gt; file in the same package as java class. My validation file looks something like this&lt;/li&gt;&lt;pre&gt;&amp;lt;!DOCTYPE validators PUBLIC &lt;br /&gt;&amp;quot;-//OpenSymphony Group//XWork Validator 1.0.2//EN&amp;quot;&lt;br /&gt;&amp;quot;http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd&amp;quot;&amp;gt;&lt;br /&gt;&amp;lt;validators&amp;gt;&lt;br /&gt;  &amp;lt;field name=&amp;quot;user.name&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;field-validator type=&amp;quot;requiredstring&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;message key=&amp;quot;dot.field.error.required&amp;quot; /&amp;gt;&lt;br /&gt;      &amp;lt;/field-validator&amp;gt;&lt;br /&gt;  &amp;lt;/field&amp;gt;&lt;br /&gt;  &amp;lt;field name=&amp;quot;user.department&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;field-validator type=&amp;quot;requiredstring&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;message key=&amp;quot;dot.field.error.required&amp;quot; /&amp;gt;&lt;br /&gt;      &amp;lt;/field-validator&amp;gt;&lt;br /&gt;  &amp;lt;/field&amp;gt;&lt;br /&gt;  &amp;lt;field name=&amp;quot;user.email&amp;quot;&amp;gt;&lt;br /&gt;      &amp;lt;field-validator type=&amp;quot;requiredstring&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;message key=&amp;quot;dot.field.error.required&amp;quot; /&amp;gt;&lt;br /&gt;      &amp;lt;/field-validator&amp;gt;&lt;br /&gt;      &amp;lt;field-validator type=&amp;quot;email&amp;quot;&amp;gt;&lt;br /&gt;          &amp;lt;message key=&amp;quot;dot.field.error.email&amp;quot; /&amp;gt;&lt;br /&gt;      &amp;lt;/field-validator&amp;gt;&lt;br /&gt;  &amp;lt;/field&amp;gt;&lt;br /&gt;&amp;lt;/validators&amp;gt;&lt;/pre&gt;&lt;/ol&gt;Main stuff is done, last thing we need is to call validation from Javascript and show error messages.&lt;br /&gt;&lt;ol&gt;&lt;li&gt;attach &lt;code&gt;click&lt;/code&gt; event to submit button&lt;/li&gt;&lt;pre&gt;$("#userformsubmit").click(function(e) {&lt;br /&gt; try {&lt;br /&gt;  var form = $("#saveform");&lt;br /&gt;  validateForm(form);&lt;br /&gt; } catch (e) { alert(e); }&lt;br /&gt; return false; // always!&lt;br /&gt;});&lt;/pre&gt;&lt;li&gt;my &lt;code&gt;validateForm&lt;/code&gt; looks like this&lt;/li&gt;&lt;pre&gt;function validateForm(form) {&lt;br /&gt; $.ajax({url: "editValidate.action", &lt;br /&gt;  data: form.serialize(),&lt;br /&gt;  success: function(data) { try {&lt;br /&gt;   var fields = $(&amp;quot;#saveform span.fielderror&amp;quot;);&lt;br /&gt;   // delete old errors, &lt;br /&gt;   // error spans are found by class name&lt;br /&gt;   fields.html(&amp;quot;&amp;quot;);&lt;br /&gt;   // set received errors, iterate through span IDs&lt;br /&gt;   data = eval(data);&lt;br /&gt;   if (data &amp;amp;&amp;amp; data.length &amp;gt; 0) {&lt;br /&gt;    for (i in data) {&lt;br /&gt;     var id = data[i].field.replace(/\./, &amp;quot;\\.&amp;quot;)+&lt;br /&gt;       &amp;quot;-error&amp;quot;;&lt;br /&gt;     var fe = $(&amp;quot;#&amp;quot;+id);&lt;br /&gt;     if (fe) { fe.html(data[i].error); }&lt;br /&gt;    }&lt;br /&gt;    return false;&lt;br /&gt;   }&lt;br /&gt;   $(&amp;quot;#saveform&amp;quot;).submit();&lt;br /&gt;   return true;&lt;br /&gt;  } catch (e) { alert(e); } }&lt;br /&gt; });&lt;br /&gt; return false;&lt;br /&gt;}&lt;/pre&gt;here for every input control I have span with id ending with &lt;code&gt;'-error'&lt;/code&gt; and class &lt;code&gt;'fielderror'&lt;/code&gt;. I still haven't tried to write macros so I implemented a separate file &lt;code&gt;fielderror.vm&lt;/code&gt;&lt;pre&gt;&amp;lt;span id=&amp;quot;$!webwork.htmlEncode($f)-error&amp;quot; &lt;br /&gt;  class=&amp;quot;fielderror&amp;quot;&amp;gt;&lt;br /&gt;#set( $err = $fieldErrors.get($f) )&lt;br /&gt;#if( $err.size() &amp;gt; 0 )&lt;br /&gt;  $err.get(0)&lt;br /&gt;#end&lt;br /&gt;&amp;lt;/span&amp;gt;&lt;/pre&gt;which I include in main page next to input fields. Example for user name field:&lt;pre&gt;#set( $f = &amp;quot;user.name&amp;quot; )&lt;br /&gt;#parse( &amp;quot;/templates/dot/userinfo/fielderror.vm&amp;quot; )&lt;/pre&gt;not so nice solution but I can live with that for now.&lt;/ol&gt;&lt;br /&gt;Next week I'll try to optimize the code. In current implementation I believe validation is called twice: 1st time in ajax, and 2nd time when form is submitted (this we can escape), for field errors I need macro..&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/492842211449076546-6337175440721166798?l=weblitera.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://weblitera.blogspot.com/feeds/6337175440721166798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://weblitera.blogspot.com/2009/05/ajax-form-validation-in-confluence.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/6337175440721166798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/6337175440721166798'/><link rel='alternate' type='text/html' href='http://weblitera.blogspot.com/2009/05/ajax-form-validation-in-confluence.html' title='Ajax form validation in Confluence plugin using a JSON request'/><author><name>dima</name><uri>http://www.blogger.com/profile/10231325970887124326</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-492842211449076546.post-8273269526987373136</id><published>2009-05-12T12:01:00.007+06:00</published><updated>2009-05-12T15:11:37.327+06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Confluence'/><category scheme='http://www.blogger.com/atom/ns#' term='Ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='Atlassian'/><category scheme='http://www.blogger.com/atom/ns#' term='servlets'/><title type='text'>Ajax in Confluence plugin using a servlet</title><content type='html'>3rd part in Confluence plugin development series..&lt;br /&gt;While working with &lt;a href="http://www.atlassian.com/software/confluence/"&gt;Confluence&lt;/a&gt; I found 2 ways to make Ajax requests:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;make requests to servlets&lt;/li&gt;&lt;li&gt;make JSON requests to XWork actions &lt;/li&gt;&lt;/ol&gt;Today I'll describe the 1st way using a servlet. In my application I had 2 comboboxes: one for select of a &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;city&lt;/span&gt; and other for a &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;department&lt;/span&gt;. A list of departments is different for every city so when client selects a city, a list of departments needs to be reloaded.&lt;br /&gt;The implementation is very simple and requires only to properly configure the servlet:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;edit your &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;atlassian-plugin.xml&lt;/span&gt; file and add a servlet definition, see &lt;a href="http://confluence.atlassian.com/display/DOC/Servlet+Plugins"&gt;Developer documentation&lt;/a&gt; for further info&lt;br /&gt;&lt;/li&gt;&lt;pre&gt;&amp;lt;servlet name="Ajax List Servlet"&lt;br /&gt; key="ajaxListServlet"&lt;br /&gt; class="dot.userinfo.users.servlets.AjaxListServlet"&amp;gt;&lt;br /&gt;  &amp;lt;description&amp;gt;Ajax List Servlet&amp;lt;/description&amp;gt;&lt;br /&gt;  &amp;lt;url-pattern&amp;gt;/getlist/&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;&amp;lt;/servlet&amp;gt;&lt;/pre&gt;&lt;li&gt;implement &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;AjaxListServlet&lt;/span&gt; class, I prefer to generate HTML code directly, but you can generate JSON array as well&lt;/li&gt;&lt;pre&gt;public class AjaxListServlet extends HttpServlet {&lt;br /&gt;  private static final long serialVersionUID = 1L;&lt;br /&gt;&lt;br /&gt;  @Override&lt;br /&gt;  protected void service(HttpServletRequest req, &lt;br /&gt;    HttpServletResponse resp)&lt;br /&gt;    throws ServletException, IOException {&lt;br /&gt;&lt;br /&gt;    String list = req.getParameter(&amp;quot;list&amp;quot;);&lt;br /&gt;&lt;br /&gt;    if (&amp;quot;dep&amp;quot;.equalsIgnoreCase(list)) {&lt;br /&gt;      String cityName = req.getParameter(&amp;quot;city&amp;quot;);&lt;br /&gt;      &lt;br /&gt;      // generate departments list HTML&lt;br /&gt;      StringBuffer sb = new StringBuffer();&lt;br /&gt;      sb.append(&amp;quot;&amp;lt;option value='' selected&amp;gt;&amp;lt;/option&amp;gt;&amp;quot;);&lt;br /&gt;      try {&lt;br /&gt;        for (DepartmentInfo dep : getDepartmentsDao().&lt;br /&gt;            getByCityName(cityName)) {&lt;br /&gt;          sb.append(&amp;quot;&amp;lt;option&amp;gt;&amp;quot;);&lt;br /&gt;          sb.append(dep.getName());&lt;br /&gt;          sb.append(&amp;quot;&amp;lt;/option&amp;gt;&amp;quot;);&lt;br /&gt;        }&lt;br /&gt;      } catch (Exception e) {&lt;br /&gt;        // handle exception&lt;br /&gt;      }&lt;br /&gt;      resp.getOutputStream().print(sb.toString());&lt;br /&gt;      return;&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  private DepartmentsDao getDepartmentsDao() {&lt;br /&gt;    return DaoFactory.getDepartmentsDao();&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;li&gt;put comboboxes for &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;city&lt;/span&gt; and &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;department&lt;/span&gt; in .vm page&lt;/li&gt;&lt;pre&gt;#tag( Select "id=city" "name='user.city'" &lt;br /&gt;  "list=citiesDao.all" &lt;br /&gt;  "listKey=name" "listValue=name" &lt;br /&gt;  "emptyOption='true'" "theme='notable'" )&lt;br /&gt;#tag( Select "id=department" "name='user.department'" &lt;br /&gt;  "list=departmentsDao.all" &lt;br /&gt;  "listKey=name" "listValue=name" &lt;br /&gt;  "emptyOption='true'" "theme='notable'" )&lt;/pre&gt;&lt;li&gt;and, finally, attach javascript to &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;city&lt;/span&gt; combobox. here is a JQuery code&lt;/li&gt;&lt;pre&gt;jQuery(function($) {&lt;br /&gt;$("document").ready(function () {&lt;br /&gt;&lt;br /&gt;$("#city").change(function() {&lt;br /&gt; var cityName = $("#city option:selected").val();&lt;br /&gt; $.get("/plugins/servlet/getlist/", &lt;br /&gt;    {list: "dep", city: cityName}, function(data) {&lt;br /&gt;  $("#department").html(data);&lt;br /&gt; });&lt;br /&gt;});&lt;br /&gt;&lt;br /&gt;});&lt;br /&gt;});&lt;/pre&gt;don't forget to include JQuery in your .vm page header&lt;pre&gt;&amp;lt;html&amp;gt;&lt;br /&gt;&amp;lt;head&amp;gt;&lt;br /&gt;  &amp;lt;title&amp;gt;...&amp;lt;/title&amp;gt;&lt;br /&gt;  #requireResource(&amp;quot;confluence.web.resources:jquery&amp;quot;)&lt;br /&gt;&amp;lt;/head&amp;gt;&lt;/pre&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/492842211449076546-8273269526987373136?l=weblitera.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://weblitera.blogspot.com/feeds/8273269526987373136/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://weblitera.blogspot.com/2009/05/ajax-in-confluence-plugin-using-servlet.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/8273269526987373136'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/8273269526987373136'/><link rel='alternate' type='text/html' href='http://weblitera.blogspot.com/2009/05/ajax-in-confluence-plugin-using-servlet.html' title='Ajax in Confluence plugin using a servlet'/><author><name>dima</name><uri>http://www.blogger.com/profile/10231325970887124326</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-492842211449076546.post-2527114382908922897</id><published>2009-05-06T10:47:00.005+06:00</published><updated>2009-05-07T10:49:47.843+06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Confluence'/><category scheme='http://www.blogger.com/atom/ns#' term='pagination'/><category scheme='http://www.blogger.com/atom/ns#' term='Atlassian'/><title type='text'>Pagination in Confluence plugin</title><content type='html'>2nd part in Confluence plugin development series..&lt;br /&gt;Confluence provides items pagination out of the box but I couldn't find any reference in developer documentation. Standard page navigation bar looks like this:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_10bysvPeeRI/SgEbSwre4NI/AAAAAAAAACg/YZ4umS37Ol0/s1600-h/pagination.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 304px; height: 37px;" src="http://1.bp.blogspot.com/_10bysvPeeRI/SgEbSwre4NI/AAAAAAAAACg/YZ4umS37Ol0/s320/pagination.png" alt="" id="BLOGGER_PHOTO_ID_5332573442902253778" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Here are the easy steps to implement it for your own list of items:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;extend your action class from &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;AbstractEntityPaginationAction&lt;/span&gt;&lt;/li&gt;&lt;pre&gt;public class ViewUsers &lt;br /&gt;  extends AbstractEntityPaginationAction {&lt;br /&gt;}&lt;/pre&gt;&lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;AbstractEntityPaginationAction&lt;/span&gt; provides &lt;br /&gt;&lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;protected bucket.core.actions.PaginationSupport paginationSupport;&lt;/span&gt; &lt;br /&gt;which you can use in your action:&lt;pre&gt;public String execute() throws Exception {&lt;br /&gt; List&lt;UserInfo&gt; users = ... // load users here&lt;br /&gt; paginationSupport.setItems(users);&lt;br /&gt; paginationSupport.setPageSize(preferredPageSize);&lt;br /&gt; return SUCCESS;&lt;br /&gt;}&lt;/pre&gt;&lt;li&gt;insert pagination macro in &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;viewusers.vm&lt;/span&gt; page&lt;/li&gt;&lt;pre&gt;#pagination($action.paginationSupport "page.action?")&lt;/pre&gt;where &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;page.action&lt;/span&gt; is the action name which will be called when you select a page. Pagination macro then adds a parameter &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;startIndex&lt;/span&gt;, that's why you need a question mark (?) at the end&lt;br /&gt;&lt;br /&gt;&lt;li&gt;implement page.action&lt;/li&gt;&lt;pre&gt;public String page() throws Exception {&lt;br /&gt; HttpServletRequest req = &lt;br /&gt;   ServletActionContext.getRequest();&lt;br /&gt; int startIndex = &lt;br /&gt;   Integer.parseInt(req.getParameter("startIndex"));&lt;br /&gt; List&lt;UserInfo&gt; users = ... // load users&lt;br /&gt; paginationSupport.setItems(users);&lt;br /&gt; paginationSupport.setPageSize(preferredPageSize);&lt;br /&gt; paginationSupport.setStartIndex(startIndex);&lt;br /&gt; return SUCCESS;&lt;br /&gt;}&lt;/pre&gt;&lt;li&gt;view current items in page&lt;/li&gt;&lt;pre&gt;#foreach( $user in &lt;br /&gt;  $action.paginationSupport.page.iterator())&lt;br /&gt; ...&lt;br /&gt;#end&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;PaginationSupport&lt;/span&gt; allows to control page size (number of items per page) so you may provide a selector in web page for page size and save it in user session.&lt;br /&gt;That's it!&lt;br /&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/492842211449076546-2527114382908922897?l=weblitera.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://weblitera.blogspot.com/feeds/2527114382908922897/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://weblitera.blogspot.com/2009/05/pagination-in-confluence.html#comment-form' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/2527114382908922897'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/2527114382908922897'/><link rel='alternate' type='text/html' href='http://weblitera.blogspot.com/2009/05/pagination-in-confluence.html' title='Pagination in Confluence plugin'/><author><name>dima</name><uri>http://www.blogger.com/profile/10231325970887124326</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://1.bp.blogspot.com/_10bysvPeeRI/SgEbSwre4NI/AAAAAAAAACg/YZ4umS37Ol0/s72-c/pagination.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-492842211449076546.post-7969543186192064783</id><published>2009-05-04T13:40:00.015+06:00</published><updated>2009-05-07T10:51:02.165+06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='persistence'/><category scheme='http://www.blogger.com/atom/ns#' term='Confluence'/><category scheme='http://www.blogger.com/atom/ns#' term='Atlassian'/><category scheme='http://www.blogger.com/atom/ns#' term='Hibernate'/><title type='text'>Database Persistence in Confluence plugin</title><content type='html'>Last month I was busy with a &lt;a href="http://www.atlassian.com/software/confluence/"&gt;Atlassian Confluence&lt;/a&gt; plugin for a local company. Next few blogs I'll devote to some problems I found in my way.&lt;br /&gt;The plugin was about browsing existing Crowd users, create new users, attach pictures to users, make directory of cities (company is distributed to several cities), departments etc., similar to &lt;a href="http://confluence.atlassian.com/display/DOC/Userinfo+Plugin+Tutorial"&gt;Userinfo example&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://confluence.atlassian.com/display/DOC/Confluence+Development+Hub"&gt;Developer documentation&lt;/a&gt; is quite useful while not complete... First thing I needed was to persist my dictionaries to database. After some discovering I've learned that Atlassian doesn't recommend using Hibernate (I couldn't attach mappings anyway) but provides Bandana instead (XML persistence based on XStream). It isn't going for me because I need to select, search, make relations between tables etc.. Make it all with Java classes?&lt;br /&gt;Fortunatelly it is still possible to use pure SQL quieries. So these are the steps for using database in Confluence:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;declare DAO bean in atlassian-plugin.xml&lt;/li&gt;&lt;pre&gt;&amp;lt;spring name="citiesDao" key="citiesDao" &lt;br /&gt;  class="dot.userinfo.cities.dao.HibernateCitiesDao"&amp;gt;&lt;b&gt;&lt;br /&gt; &amp;lt;property name="sessionFactory"&amp;gt;&lt;br /&gt;  &amp;lt;ref bean="sessionFactory"/&amp;gt;&lt;br /&gt; &amp;lt;/property&amp;gt;&lt;/b&gt;&lt;br /&gt;&amp;lt;/spring&amp;gt;&lt;br /&gt;&lt;/pre&gt;where &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;sessionFactory&lt;/span&gt; is Hibernate's session factory provided by Confluence;&lt;li&gt;implement DAO class and derive it from &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;HibernateObjectDao&lt;/span&gt; (provided by Atlassian)&lt;/li&gt;&lt;pre&gt;public class HibernateCitiesDao &lt;br /&gt;  extends &lt;b&gt;HibernateObjectDao&lt;/b&gt; {&lt;br /&gt;public Collection&lt;CityInfo&gt; getAll() {&lt;br /&gt; List&lt;cityinfo&gt; result = new ArrayList&lt;CityInfo&gt;();&lt;br /&gt; Session session = null;&lt;br /&gt; try {&lt;br /&gt;  session = getSessionFactory().openSession();&lt;br /&gt;  ResultSet rs = session.connection().&lt;br /&gt;   prepareStatement("select * from city").executeQuery();&lt;br /&gt;  while (rs.next()) {&lt;br /&gt;   CityInfo o = new CityInfo();&lt;br /&gt;   fill(rs, o);&lt;br /&gt;   result.add(o);&lt;br /&gt;  }&lt;br /&gt; } finally {&lt;br /&gt;  if (session != null) {&lt;br /&gt;   session.disconnect();&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; return result;&lt;br /&gt;}&lt;br /&gt;}&lt;/pre&gt;&lt;li&gt;connect DAO to an action in atlassian-plugin.xml:&lt;/li&gt;&lt;pre&gt;&amp;lt;action name="viewcities"&lt;br /&gt;   class="dot.userinfo.cities.action.ViewCities"&amp;gt;&lt;br /&gt; &lt;b&gt;&amp;lt;external-ref name="citiesDao"&amp;gt;citiesDao&amp;lt;/external-ref&amp;gt;&lt;/b&gt;&lt;br /&gt; &amp;lt;result name="success" type="velocity"&amp;gt;&lt;br /&gt;  /templates/dot/userinfo/cities/viewcities.vm&amp;lt;/result&amp;gt;&lt;br /&gt;&amp;lt;/action&amp;gt; &lt;br /&gt;&lt;/pre&gt;Class &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;ViewCities&lt;/span&gt; must have a setter (and getter if you want to use it directly in page) methods for &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;citiesDao&lt;/span&gt;:&lt;pre&gt;public void setCitiesDao(CitiesDao citiesDao) {&lt;br /&gt; this.citiesDao = citiesDao;&lt;br /&gt;}&lt;br /&gt;public CitiesDao getCitiesDao() {&lt;br /&gt; return citiesDao;&lt;br /&gt;}&lt;/pre&gt;&lt;li&gt;use cities on the page &lt;span style="font-family:courier new;color: rgb(102, 102, 102);font-size:85%;" &gt;viewcities.vm&lt;/span&gt;:&lt;/li&gt;&lt;pre&gt;#set($cities = $action.citiesDao.all)&lt;br /&gt;#foreach( $item in $cities.iterator())&lt;br /&gt; ...&lt;br /&gt;#end&lt;/pre&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/492842211449076546-7969543186192064783?l=weblitera.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://weblitera.blogspot.com/feeds/7969543186192064783/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://weblitera.blogspot.com/2009/05/database-persistence-in-atlassian.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/7969543186192064783'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/7969543186192064783'/><link rel='alternate' type='text/html' href='http://weblitera.blogspot.com/2009/05/database-persistence-in-atlassian.html' title='Database Persistence in Confluence plugin'/><author><name>dima</name><uri>http://www.blogger.com/profile/10231325970887124326</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-492842211449076546.post-8581656363833395822</id><published>2009-05-04T10:14:00.009+06:00</published><updated>2009-05-04T13:02:03.650+06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='bilingual reading'/><category scheme='http://www.blogger.com/atom/ns#' term='learning'/><category scheme='http://www.blogger.com/atom/ns#' term='languages'/><title type='text'>Bilingual reading. Learning languages</title><content type='html'>Hi,&lt;br /&gt;my first blog is about learning foreign languages. As you may already have guessed English is not my native language, but I decided to write in English just in order to have another language practice :)&lt;br /&gt;&lt;br /&gt;At school I studied German. You may consider me stupid, but after 6 years of study I could hardly read some simple texts and couldn't speak German at all.&lt;br /&gt;I prefer to blame my teacher for that while she couldn’t speak herself, I even believe she'd never been in Germany. All we did at lessons is just that we read some stupid dialogs which always made me boring.&lt;br /&gt;After graduating from school I started to believe that foreign languages are something too hard for me. But while I learned computer science I had to read lots of documentation in English for which there was no translation.&lt;br /&gt;&lt;br /&gt;When I began to study English for myself (about 10 years ago) I found out that adaptive reading gives best and fastest result. Already after 6 months I could easily read any documentation and some classics.&lt;br /&gt;&lt;br /&gt;I'm not sure about the term so let me call it 'bilingual reading' or 'adaptive reading'. It means you can start reading any foreign book after you've got some basic grammar knowledge.&lt;br /&gt;&lt;br /&gt;When I came to this idea for the first time I was deeply impressed!&lt;br /&gt;&lt;br /&gt;The idea is that you have same text in two languages. At first you read an article in your native language, then same article in foreign language. Don't care about all unknown words, idioms or slang. Just read the text and don't stop.&lt;br /&gt;After some time you begin to understand that you've learned lots of words and it cost you nothing.&lt;br /&gt;Then you can try to read an article in foregn language first and understand what's there, then read a translation to be sure.&lt;br /&gt;Still you don't learn words or grammar, they just come to you by and by.&lt;br /&gt;&lt;br /&gt;Soon after you'll not need a translation any more, just occasionally look up a word or a phrase.&lt;br /&gt;&lt;br /&gt;I found several web sites (mostly in russian) wholly devoted to bilingual reading, which provide some dozens of selected books in Microsoft Word format. I can imagine how much effort it took to produce these books!&lt;br /&gt;&lt;br /&gt;For me it was not a solution, so I decided to make my own automated service :)&lt;br /&gt;&lt;br /&gt;I wanted a solution which would generate bilingual documents automatically out of several translations. So I made a small website written in PHP. Almost 2 years I used it at home on my PC and I enjoyed reading my favorite books. About a year ago I finally decided to publish it on the internet so I could read from mobile phone while out from home for business..&lt;br /&gt;&lt;br /&gt;So let me introduce &lt;a href="http://www.weblitera.com/"&gt;http://www.weblitera.com/&lt;/a&gt;. Unfortunatelly international copyright law forbids to publish modern authors (living after 1950) so this website became a collection of classical literature :)&lt;br /&gt;&lt;br /&gt;It's better than nothing and anyway there are lots of masterpieces among classics which I haven't read before (I bet you too).&lt;br /&gt;&lt;br /&gt;Here is how bilingual reading looks like:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_10bysvPeeRI/Sf6HUoLTcMI/AAAAAAAAACA/EADMbXC23Dg/s1600-h/bilingual+reading.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 230px;" src="http://2.bp.blogspot.com/_10bysvPeeRI/Sf6HUoLTcMI/AAAAAAAAACA/EADMbXC23Dg/s320/bilingual+reading.jpg" alt="" id="BLOGGER_PHOTO_ID_5331847797305209026" border="0" /&gt;&lt;/a&gt;In this picture one column has english text and other is in spanish. If a book has several translations (lots of books have 4+ translations) it may be read in any pair of available languages.&lt;br /&gt;&lt;br /&gt;With help of &lt;a href="http://translate.google.com/"&gt;Google Translator&lt;/a&gt; you can translate any word or sentence by hovering with a mouse pointer. Recently Google introduced &lt;a href="http://www.google.com/dictionary"&gt;Dictionary&lt;/a&gt; but it has no public API (yet?). I would use it when it becomes available..&lt;br /&gt;&lt;br /&gt;&lt;a href="http://imtranslator.net/"&gt;ImTranslator&lt;/a&gt;  service provides TTS (Text to Speech). Select any text and click an ImTranslator image:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_10bysvPeeRI/Sf6RkxCBWtI/AAAAAAAAACY/-btwsThKJ1M/s1600-h/TTS.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 320px; height: 60px;" src="http://1.bp.blogspot.com/_10bysvPeeRI/Sf6RkxCBWtI/AAAAAAAAACY/-btwsThKJ1M/s320/TTS.jpg" alt="" id="BLOGGER_PHOTO_ID_5331859069676378834" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Everyone interested is welcome to visit it, maybe somebody will find it helpful :)&lt;br /&gt;Oh yes, currently there are about 200 original works in 7 languages (english, german, french, spanish, italian, russian and chinese), totally more that 500 translations. More coming...&lt;br /&gt;&lt;br /&gt;Let me know if you have some great ideas how to extend this website further..&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/492842211449076546-8581656363833395822?l=weblitera.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://weblitera.blogspot.com/feeds/8581656363833395822/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://weblitera.blogspot.com/2009/05/bilingual-reading-learning-languages.html#comment-form' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/8581656363833395822'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/492842211449076546/posts/default/8581656363833395822'/><link rel='alternate' type='text/html' href='http://weblitera.blogspot.com/2009/05/bilingual-reading-learning-languages.html' title='Bilingual reading. Learning languages'/><author><name>dima</name><uri>http://www.blogger.com/profile/10231325970887124326</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://2.bp.blogspot.com/_10bysvPeeRI/Sf6HUoLTcMI/AAAAAAAAACA/EADMbXC23Dg/s72-c/bilingual+reading.jpg' height='72' width='72'/><thr:total>1</thr:total></entry></feed>
