JSP based components in Spring Surf

In my last blogpost we got Java backed webscripts working, now let’s try something completely different: defining page snippet as a component in .JSP. Just let’s continue with our last code, which is possible to download, but it’s not necessary, it’s possible to build it all on green meadow (aka after project in roo is … Pokračovat ve čtení „JSP based components in Spring Surf“

In my last blogpost we got Java backed webscripts working, now let’s try something completely different: defining page snippet as a component in .JSP. Just let’s continue with our last code, which is possible to download, but it’s not necessary, it’s possible to build it all on green meadow (aka after project in roo is created and surf installed).

First thing to do is to add dependency for jstl into Maven’s pom.xml and then update maven dependencies and refresh our project in Eclipse.

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>com.springsource.javax.servlet.jsp.jstl</artifactId>
    <version>1.2.0</version>
</dependency>

Now you can get glue on Spring forums, or trust me and create new component type with JSP Renderer: it’s needed to create new directory – /WEB-INF/classes/surf/site/component-types and inside new component type configuration – test.xml.

<?xml version="1.0" encoding="UTF-8"?>
<component-type>
    <id>test</id>
    <title>Test Component Type</title>
    <description>Test Component Type</description>

    <processor mode="view">
        <id>jsp</id>
        <jsp-path></jsp-path>
    </processor>
</component-type>

Jsp-path is at this time undefined, because we haven’t created any page yet. So let’s do something with that. We need to create new template and new template instance:

roo> surf template create --path test
Created SRC_MAIN_WEBAPP/WEB-INF/templates/test.ftl
roo> surf template instance create --id test --path templates --template test
Created SRC_MAIN_WEBAPP/WEB-INF/templates/test.xml

At this time we have Freemarker template (test.ftl) and template instance (test.xml) inside directory /WEB-INF/templates. We need to edit those files. First let’s add following code into test.ftl. There are four regions defined – three of them inside template scope (already defined by installation of Surf addon) and one with page scope, which we’ll define and bind later.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>${page.title}</title>
  </head>

  <body>
    <div id="page">
      <div id="header">
        <@region id="header" scope="template" />
      </div>
      <div id="horznav">
        <@region id="horznav" scope="template" />
      </div>
      <div id="content">
        <@region id="content" scope="page" />
      </div>
      <div id="footer">
        <@region id="footer" scope="template" />
      </div>
    </div>
  </body>
</html>

Following code (template instance) is definition of components in template scope and is also located in templates directory: test.xml.

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<template-instance>
  <title>test</title>
  <description>Instance of test</description>
  <template-type>test</template-type>
  <components>
    <component>
      <region-id>header</region-id>
      <url>/company/header</url>
    </component>
    <component>
      <region-id>horznav</region-id>
      <url>/navigation/horizontal</url>
    </component>
    <component>
      <region-id>footer</region-id>
      <url>/company/footer</url>
    </component>
  </components>
</template-instance>

Now we can create new page based on previously defined template-instance and add link to our new page to horizontal bar. For those actions we can use roo:

roo> surf page create --id test --path pagestest --templateInstance test
Created SRC_MAIN_WEBAPP/WEB-INF/pages/test
Created SRC_MAIN_WEBAPP/WEB-INF/pages/test/test.xml

roo> surf page association create --sourceId home --destId test
Managed SRC_MAIN_WEBAPP/WEB-INF/pages/home/home.xml

As written in roo output, directory test inside pages is created and test.xml within. This file is page configuration and we have to define our content for region defined in template. But not necessary, it’s good idea to check our work now. So let’s try to Run it on server… It’s ugly, but it works! 🙂

First try
First try

That’s cool, but our aim is to create working .jsp page. How to do that? What about simply create that page? 🙂 So let’s create test.jsp inside /WEB-INF/pages/test and fill it with some simple code:

<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt'%>

<%@ page import="java.util.*"  %>
<%@ page contentType="text/html;charset=UTF-8" language="java"%>

<h1>Test jsp</h1>

<%
  String someText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";

  List<String> l = new ArrayList<String>();
  l.add("Maecenas iaculis odio id eros fermentum non tempus erat interdum.");
  l.add("Sed mi orci, dignissim vitae cursus hendrerit, congue sed odio. ");
  l.add("Vivamus quam diam, dapibus sit amet placerat a, tempus at magna.");

  session.setAttribute("someText", someText);
  session.setAttribute("l", l);
%>

<p>${someText }</p>

<c:forEach var="x" items="${l }" >
    <p>${x}</p>
</c:forEach>

That’s our page. Now we need to connect it with previously created component type – /WEB-INF/classes/surf/site/component-types/test.xml. In time of creation we omit jsp-path, so fill it up now:

<jsp-path>/WEB-INF/pages/test/test.jsp</jsp-path>

Last thing to do is to put our shiny new component-type into page. In template we defined region, which is filled in page scope. That’s that components section in page config – /WEB-INF/pages/test/test.xml. It’s good idea to add some title and some styling like border and background thanks to chrome element:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<page>
  <id>test</id>
  <title>test</title>
  <template-instance>test</template-instance>
  <authentication>none</authentication>
  <components>
    <component>
      <region-id>content</region-id>
      <component-type-id>test</component-type-id>
      <chrome>box</chrome>
      <title>JSP inside</title>
    </component>
  </components>
</page>

After rebuild and redeploy we have our jsp component working, as you can see at http://localhost:8080/webscripts/test. Congratulations, good work!

Final application look
Final application look

Ah, in case you got lost, there is package with working sourcecode for this whole example.

Freemarker templates and java backed webscripts

Wondering about output to freemarker template from java backed webscript in Spring Surf? You’re on the right place, it’s simple so I’ll try to describe it on a few lines. For first I suppose some prerequisites you have: you have Spring Roo and (or) Springsource Tool Suite (or Eclipse with plugin), you have Spring surf … Pokračovat ve čtení „Freemarker templates and java backed webscripts“

Wondering about output to freemarker template from java backed webscript in Spring Surf? You’re on the right place, it’s simple so I’ll try to describe it on a few lines. For first I suppose some prerequisites you have:

Ok, let’s get it done quickly! Create new directory, for example webscripts. Go into it, fire up roo, create a new project, install surf extension into it and create an Eclipse project:

project --topLevelPackage cz.shmoula.webscripts
surf install
perform eclipse

Now you should have base skeleton of our future app (you can also download it here) and you can import it into SpringSource Tool Suite.

Webscripts are located in src/main/webapp/WEB-INF/webscripts folder, so open that folder and create two files: example.get.html.ftl (template) and example.get.desc.xml (description).

<p>logged user: ${userId}</p>
<webscript>
      <shortname>Example</shortname>
      <description>Sample java backed webscript</description>
      <url>/example</url>
</webscript>

Now we have to create our java source code. It’s not bad idea to put webscripts to their own package, for example webscript. In that package create a new class, which extends org.springframework.extensions.webscripts.AbstractWebScript – Example. By doing that we have to implement an execute method, which is called, when webscript is executed:

@Override
public void execute(WebScriptRequest req, WebScriptResponse res)
    throws IOException {
}

Let’s fill that method. We need a model map, which holds values as pair (String)key – (Object)value. For our desired function we also need to get HttpServletRequest:

Map<String, Object> model = new HashMap<String, Object>();
HttpServletRequest httpRequest = ServletUtil.getRequest();

Now to fill up model (aka our function). Let’s find out if there is logged in user and if so, write out it’s userId:

if(AuthenticationUtil.isAuthenticated(httpRequest)){
      model.put("userId", AuthenticationUtil.getUserId(httpRequest));
}else{
      model.put("userId", "guest");
}

Final step is to put out our model. We need to get writer and renderTemplate, which we specified by its path (and also flush and close our writer):

Writer writer = res.getWriter();
String templatePath = "webscripts/example.get.html.ftl";
renderTemplate(templatePath, createTemplateParameters(req, res, model), writer);

writer.flush();
writer.close();

Last thing we have to do is to let Surf know about our shiny new webscript. You can add new bean directly inside applicationContext, but it’s not bad idea to keep them separately. So in src/main/webapp/WEB-INF/config create new Spring Bean Definition file – custom-webscripts-config.xml and put inside new bean definition:

<bean id="webscript.webscripts.example.get" class="cz.shmoula.webscript.Example" parent="webscript"/>

Now we have to import that new config: in the same directory is surf-config.xml file, so let’s add there new import:

<import resource="custom-webscripts-config.xml"/>

At this time we can run our project and visit http://localhost:8080/webscripts/example, which shows following page:

Image and video hosting by TinyPic

It’s possible to login (if you have set up user factory) as an Alfresco user and after that another userId is shown. I’m planning to write new blogpost about custom user factory and stuff like that, so be patient please. Finally, you can download source code for this example at this link. Structure of project is shown at following screenshot (note that red dots – those are modified files):

Image and video hosting by TinyPic

Finally, try to experiment with some examples from Alfresco wiki and look forward to next blogpost.

Webscripts v Alfrescu a Cache

Dnes jsem málem zase přišel o rozum při snaze rozchodit v IntranetExploaderu některé flashové součásti aplikace, běžící v Alfrescu, které jsem překlopil na zabezpečený režim přenosu za použití SSL. Šlo o to, že flash odmítl načíst vzdálené XML (aka výstup webscriptu). Po celoodpoledním googlení jsem našel prvotní nakopávku. Takže jsem natáhl plugin pro firefox, který … Pokračovat ve čtení „Webscripts v Alfrescu a Cache“

Dnes jsem málem zase přišel o rozum při snaze rozchodit v IntranetExploaderu některé flashové součásti aplikace, běžící v Alfrescu, které jsem překlopil na zabezpečený režim přenosu za použití SSL. Šlo o to, že flash odmítl načíst vzdálené XML (aka výstup webscriptu).

Po celoodpoledním googlení jsem našel prvotní nakopávku. Takže jsem natáhl plugin pro firefox, který mě jen utvrdil v tom, že výstup (obr. 1) generovaný webscripty Alfresca se neslučuje s bugou v IE. Ve wiki Alfresca sice něco o cache je, ale nějak jsem z toho nepochytil, jaké konkrétní hodnoty mám nastavit.

Obr. 1: hlavičky, se kterými má IE problémy


Takže jsem ještě chvíli pokusničil, googlil a nadával, až jsem definitivně kápnul na kombinaci, kterou dokáže přelouskat i IE a flash mi konečně funguje korektně i v tomhletom „prohlížeči“. Je nutné odstranit položku „Pragma“ a položce „Cache-control“ nastavit hodnotu no-store a must-revalidate. V definici webscriptu alfresca (nazev.get.desc.xml) se jedná o tři položky:

<cache>

<mustrevalidate>true</mustrevalidate>

<public>true</public>

<never>false</never>

</cache>

Tato definice přidaná k popisu webscriptu generuje hlavičku výstupu webscriptu už správně, jak je vidět na Obr. 2.

Obr. 2: správné nastavení chování cache

Teď už vše funguje tak jak má a mě se potvrzuje oblíbený fakt potvrzující, že Microsoft by se měl věnovat jedné jediné věci, kterou jakž-takž dělal relativně dobře – výrobě počítačových myší:

  • Vývoj aplikace – x člověkojednotek
  • Zjišťování, proč to nejede v IE – 2*x člověkojednotek
  • Snaha to nějak obechcat – min. 2*x člověkojednotek

PS pokud někdo také tápe a už vyzkoušel všechno, nechť kroky své sune tímto směrem a též směrem k nastavení IE – Možnosti Internetu / Upřesnit a odtržení položky „Neukládat šifrované stránky na disk“.

    HOWTO Alfresco PHP Library and xUbuntu

    I tried to install Alfresco PHP Library and install it on my kubuntu developing box and it's really very easy to get it running! Instalation procedure for windows is described in Alfresco wiki, but there are some differencies.

    I tried to install Alfresco PHP Library and install it on my kubuntu developing box and it’s really very easy to get it running! Instalation procedure for windows is described in Alfresco wiki, but there are some differencies.

    To get it running it’s needed to install apache and php:

    sudo apt-get install apache2 libapache2-mod-php5

    Then you need to download PHP Library from Sourceforge. After that you can unpack it somewhere; good place is for example /usr/share/php/alfresco-php-library. Now it’s not bad to set virtual directory to that directory. So edit your /etc/apache2/conf.d/alias file and add something like this:

    Alias /Alfresco/ /usr/share/php/alfresco-php-library/

    <Directory /usr/share/php/alfresco-php-library/>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride All
    Order allow,deny
    Allow from all
    </Directory>

    Also including this directory automaticaly (/etc/php5/apache2/php.ini) should be cool:

    include_path = „.:/usr/share/php/alfresco-php-library“

    Now you need to restart apache:

    /etc/init.d/apache2 restart

    And edit configuration file in alfresco-php-library/Examples/config.php.

    $repositoryUrl = "http://localhost:8080/alfresco/api";
    $userName = "admin";
    $password = "admin";

    After all that you can try to open web browser and point it to http://localhost/Alfresco/Examples/SimpleBrowse/ and it should work 😎


    Image and video hosting by TinyPic

    Sending content to java webscript via POST method

    Yesterday I stucked on problem of getting data from WebScriptRequest, which were sent by POST method. There is some bug in Apache Tomcat, so there is a simple workaround described for example here. My task was to create webscript, which tries to find a file in repository (or create it, if it doesn't exist) and to write content inside – data of POST request.

    Yesterday I stucked on problem of getting data from WebScriptRequest, which were sent by POST method. There is some bug in Apache Tomcat, so there is a simple workaround described for example here. My task was to create webscript, which tries to find a file in repository (or create it, if it doesn’t exist) and to write content inside – data of POST request.

    My webscript is Java backed, so only description in WEB-INF/classes/alfresco/templates/client/webscripts/org/alfresco/archivace is needed:

    <webscript>
    <shortname>SaveModif file</shortname>
    <description>Ulozi dorucena data do *.modif.xml</description>
    <url>/archivace/savemodif?nodeRef={noderef}</url>
    <authentication>user</authentication>
    <transaction>required</transaction>
    </webscript>

    Also definition in web-scripts-application-context.xml:

    <bean id=“webscript.org.alfresco.archivace.savemodif.post“ class=“cz.shmoula.webscripts.SaveModif“ parent=“webscript“>
    <property name=“authenticationComponent“ ref=“AuthenticationComponent“/>
    <property name=“nodeService“ ref=“NodeService“/>
    <property name=“searchService“ ref=“SearchService“/>
    <property name=“fileFolderService“ ref=“FileFolderService“/>
    <property name=“contentService“ ref=“ContentService“/>
    </bean>

    Now let’s get Node object itself:

    // try to get DescriptionNode
    Node xmlNode = getDescriptionNode(nodeRef);

    // if DescriptionNode doesn’t exist, create one
    if (xmlNode == null) {
    String path = noda.getName();
    String filename = path.substring(0, path.lastIndexOf(„.“)) + „.modif.xml“;
    NodeRef spaceRef = nodeService.getPrimaryParent(noda.getNodeRef()).getParentRef();
    xmlNode = createDescriptionNode(spaceRef, filename);
    }

    At this time in xmlNode is Node object – either existing one, or created one, so let’s write that request content inside:

    ContentWriter writer = contentService.getWriter(xmlNode.getNodeRef(), ContentModel.PROP_CONTENT, true);
    writer.putContent(req.getContent().getInputStream());

    Simple, but sometimes it doesnt work. Why? Due to that bug mentioned in perex. So how are data sent through POST method? Let’s try it with wget:

    wget –post-data=’pokus‘ –http-user=admin –http-password=admin http://localhost:8080/alfresco/service/archivace/savemodif?nodeRef=workspace://SpacesStore/91919928-3709-4e2e-a829-b0be46976e42

    Captured request looks like this

    Image and video hosting by TinyPic

    As you can see, everything looks ok. But getContent() in our webscript is empty. Why? there is no content – there are just parameters, due to application/x-www-form-urlencoded content type. So req.getParameterNames() returns String[] array in which are in our case two parameters: {{nodeRef}, {pokus}}. This is not what we want, because length ot these parameter names may be limited (2M?). So let’s use mentioned advice – change content-type:

    wget –post-data=’pokus‘ –header=’Content-Type: text/xml‘ –http-user=admin –http-password=admin http://localhost:8080/alfresco/service/archivace/savemodif?nodeRef=workspace://SpacesStore/91919928-3709-4e2e-a829-b0be46976e42

    And now it works! 🙂 Sniffed request looks like before, just content-type header line had changed:

    Image and video hosting by TinyPic

    Now is possible to send inputStream of content to Writer:

    writer.putContent(req.getContent().getInputStream());

    At this time req.getParameterNames() has just one parameter – {{nodeRef}} and content is really saved in data field.

    Delete dialog workaround

    Write this one, or not to write this blogpost? That is the question :-). But I would like to simply remember for some things i did, so let's do it! In my DMS I have some multimedia content saved and this content has attached description in xml file. Also I have my own space view (more info here), so I show just that multimedia content. In case of deletion that content I want to delete that xml too, so how to do that?

    Write this one, or not to write this blogpost? That is the question :-). But I would like to simply remember for some things i did, so let's do it! In my DMS I have some multimedia content saved and this content has attached description in xml file. Also I have my own space view (more info here), so I show just that multimedia content. In case of deletion that content I want to delete that xml too, so how to do that?

    First we need to create an action (it's just a copy of original action ;-)) specification in web-client-config and name it – in my case "delete_doc_shm":

    <action id="delete_doc_shm">
        <permissions>
            <permission allow="true">Delete</permission>
        </permissions>
        <evaluator>org.alfresco.web.action.evaluator.DeleteDocEvaluator</evaluator>
        <label-id>delete</label-id>
        <image>/images/icons/delete.gif</image>
        <action-listener>#{BrowseSessionBean.deleteFile}</action-listener>
        <params>
            <param name="id">#{actionContext.id}</param>
            <param name="ref">#{actionContext.nodeRef}</param>
        </params>
    </action>

    It's pretty simple, just a definition of action with Evaluator and wanted permissions. I wrote a blogpost about this a year about :-). Now I have an action, so I need to add it into some group, or view link directly. Action groups are defined in web-client-config too:

    <action-group id="session_browse">
        <show-link>false</show-link>
        <style-class>inlineAction</style-class>
        .  .  .
         <action idref="delete_doc_shm" />
    </action-group>

    And view of this group is realised by actions component (r variable is variable defined by richListComponent, library prefix r is Alfresco's repo taglib):

    .  .  .
    <r:actions id="actions_menu_1" value="session_browse" context="#{r}" showLink="false" styleClass="inlineAction" />
    .  .  .  

    The result's like this (there's no difference in original view of that actions menu and this, but functionality – see below ;-)):

    Image and video hosting by TinyPic

    Now some application logic: In case of pressing our delete butto, BrowseSessionBean.deleteFile() method is called. This method just do same things that original BrowseBean.deleteFile() does, but redirects to my own dialog:

    public void deleteFile(ActionEvent event) {
        .  .  .  some foo – bar stuff  .  .  .
        FacesContext fc = FacesContext.getCurrentInstance();
        NavigationHandler navigationHandler = fc.getApplication().getNavigationHandler();
        navigationHandler.handleNavigation(fc, null, "dialog:deleteVcfFile");
    }

    This dialog is defined in web-client-config like

    <dialog
        name="deleteVcfFile"
        page="/jsp/dialog/delete.jsp"
        managed-bean="DeleteVcfContentDialog"
        icon="/images/icons/delete_large.gif"
        title-id="delete_file"
        description-id="delete_file_info" />

    and points to DeleteVcfContentDialog bean:

    <managed-bean>
        <description>The bean that backs up the Delete Content Dialog</description>
        <managed-bean-name>DeleteVcfContentDialog</managed-bean-name>
        <managed-bean-class>cz.shmoula.dialog.DeleteVcfContentDialog</managed-bean-class>
        <managed-bean-scope>session</managed-bean-scope>
        <managed-property>
            <property-name>navigator</property-name>
            <value>#{NavigationBean}</value>
        </managed-property>
        .  .  .  more properties  .  .  .
    </managed-bean>

    Class managing this bean extends DeleteContentDialog and has only one method – finishImpl. See following source, it's commented:

    public class DeleteVcfContentDialog extends org.alfresco.web.bean.content.DeleteContentDialog {

        @Override
        protected String finishImpl(FacesContext context, String outcome)throws Exception {
            // get the content to delete
            VcfNode node = new VcfNode(this.browseBean.getDocument());
            if (node != null) {
                if (ContentModel.TYPE_MULTILINGUAL_CONTAINER.equals(node.getType())) {
                    // delete the mlContainer and its translations
                    getMultilingualContentService().deleteTranslationContainer(node.getNodeRef());
                } else {
                    // getting attached xml node
                    Node descriptionXml = node.getXmlNode();
                    // and if xml exists, delete it!
                    if(descriptionXml != null)
                        this.getNodeService().deleteNode(descriptionXml.getNodeRef());
                    
                    // delete the node
                    this.getNodeService().deleteNode(node.getNodeRef());
                }
            }
            return outcome;
        }
    }

    As I can see, it'll be much better to implement some transactions inside this method to 'do some atomicity' 8-). VcfNode is my Node extension, I'll try to describe it in next blogpost.

    So, it's pretty easy to workaround system dialogs thanks to Alfresco source code. But sometimes you can find things, which looks like there is no source in sdk 🙁 but google always helps!

    Customizing Simple Search

    Some time ago I made customized advanced search results view – no two boxes like in standard Alfresco, but three boxes – the third one for viewing special content type. Now I wanted to add this results screen as a default result of searching via the simple search box.

    Some time ago I made customized advanced search results view – no two boxes like in standard Alfresco, but three boxes – the third one for viewing special content type. Now I wanted to add this results screen as a default result of searching via the simple search box.

    When I firstly saw r:simpleSearch component and no documentation, I wasn't happy. But after while I realized that it's very simple, thanks to actionListener parameter on that component:

     <r:simpleSearch id="search" actionListener="#{BrowseSessionBean.search}" />

    This component creates that very known search box

    Image Hosted by ImageShack.us

    That action listener, search, is defined in BrowseBean, but I did my own (I copied and edited it :-)). All what this listener does is just setting Search Context and then redirects to results page – returns adequate outcome string to faces navigator.

     public void search(ActionEvent event){
            UISimpleSearch search = (UISimpleSearch)event.getComponent();
            this.navigator.setSearchContext(search.getSearchContext());
            
            FacesContext context = FacesContext.getCurrentInstance();
            context.getApplication().getNavigationHandler().handleNavigation(context, null, "browseSearchResults");
        }

     Navigation rule in faces config navigates in this case from everywhere to my own search results page thanks to this rule:

    <navigation-rule>
            <from-view-id>/jsp/*</from-view-id>
            <navigation-case>
                <from-outcome>browseSearchResults</from-outcome>
                <to-view-id>/jsp/archivace/view-search-results.jsp</to-view-id>
            </navigation-case>
    </navigation-rule>

    That's all, view-search-results is up to you 🙂

    Playing with titlebar and navigator II

    In last blogpost I wrote about titlebar and menu in it. Also I promised some navigator hacking, which is more complex (and crazy has many javascript code) and amazing, so I decided to write whole blogpost on it. Here we are go!

    In last blogpost I wrote about titlebar and menu in it. Also I promised some navigator hacking, which is more complex (and crazy has many javascript code) and amazing, so I decided to write whole blogpost on it. Here we are go!

    Navigator jsp is pretty simple, but it's not plus for us, because whole navigator is a component – r:navigator. That should not be a problem, but this component has no documentation and what is much worse, this component's aim is just to view that simple menu with Guest home, Alfresco home, My home and Company home. Nothing less, nothing more, no editings.So what to do with that crap? Let's do some hacking! You can see my result at following picture, I added one more link:

     

    Image and video hosting by TinyPic    Image and video hosting by TinyPic

     

    Navigator.jsp is in /jsp/sidebar/ and at start there is just that component:

     <r:navigator id="navigator" activeArea="#{NavigationBean.toolbarLocation}" />

     This component renders ahref links inside divs, like this:

    <div id="navigator">
          <div><a href="foo">bar</a></div>
          <div><a href="bar">foo</a></div>
              . . .
    </div>

    So I need to insert my own container with my link (both styled some way). ActionLink doesn't take 'class' attribute, so I need to inject that attribute with simple javascript:

     <a:booleanEvaluator value="#{BrowseSessionBean.isArchivaceExist}">
        <f:verbatim>
            <div id="navigatorArchivaceBox" class="sidebarButton" style="background-image: url(/alfresco/images/parts/navigator_grey_gradient_bg.gif);">
        </f:verbatim>
        <a:actionLink id="navigatorArchivaceLink" value="Archivace" href="#{BrowseSessionBean.archivaceUrl}"/>
        <f:verbatim>
                <script type="text/javascript">
                       var link = document.getElementById("navigatorArchivaceLink");
                       link.setAttribute("class","sidebarButtonLink");
                </script>
            </div>
        </f:verbatim>
    </a:booleanEvaluator>

    I personally hate that opening div inside verbatim tag and also that closing div, but what I can do else :-/. So this simple hack added my own link into navigator menu, it's clickable, it's styled, that link works, but that box is not selectable. After deploy this probably look like left picture a few rows higher. All that code is placed inside evaluator, because I need to have destination space accessible, to display this link in menu.

    So next thing to do is to deselect all boxes and all links after 'my link' is clicked and also select my box and my link. Sounds simple, but it isn't thanks to 'My Alfresco' link. I'm doing all this stuff only when I'm at the right place (my Archivace space), so I need evaluator:

    <a:booleanEvaluator value="#{BrowseSessionBean.isInsideArchivace}">
    <f:verbatim>

    Inside this evaluator I have simple javascript, which goes through elemnts under navigator container – links and divs – and for each set 'unselected' style:

    <script type="text/javascript">
        // disabling 'selected' style at all links
        var links = document.getElementById("navigator").getElementsByTagName('a');
        for(var i=0; i<links.length; i++){
            links[i].setAttribute("class","sidebarButtonLink");
        }

        // disabling 'selected' style at all boxes
        var links = document.getElementById("navigator").getElementsByTagName('div');
        for(var i=0; i<links.length; i++){
            links[i].setAttribute("class","sidebarButton");
            links[i].setAttribute("style","background-image: url(/alfresco/images/parts/navigator_grey_gradient_bg.gif)");
        }

    Now it's time to set 'selected' style just for my (selected) link. I know its ids, so I can access them directly through getElementById:

    document.getElementById("navigatorArchivaceBox").setAttribute("style","background-image: url(/alfresco/images/parts/navigator_blue_gradient_bg.gif)");
    document.getElementById("navigatorArchivaceLink").setAttribute("class","sidebarButtonSelectedLink");

    And now all I have to do is 'just' to create evaluator – isInsideArchivace. That is not so hard – just getting current node and asking, if it has wanted aspect ;-).

    public boolean getIsInsideArchivace(){
        if(this.getCurrentLocation()==null)return false;
        return this.navigator.getCurrentNode().hasAspect(cz.shmoula.ArchivaceModel.archSummary);
    }

    Now it's all doomed to work, like on first and second image :-). BUT – 'My Alfresco' is not an ordinary space, it's some virtual space or something, so when I left my 'Archivace' space by clicking on 'My Alfresco', My Alfresco screen is opened, but selected option is still 'Archivace'. What to do? Look at breadcrumb, because when there is node, it's ordinary space. But when there's nothing, it's probably 'My Alfresco' space (if not, it doesn't matter, because it is not my 'Archivace' space) and it's neccessarynot to display 'Archivace' as selected node. Soall that is inside that getCurrentLocation, which fragments are stolen somewhere from Alfresco sources:

     public String getCurrentLocation(){
            List<IBreadcrumbHandler> location = this.navigator.getLocation();
            String name = null;
              if (location.size() != 0){
                  for (int i=0; i<location.size(); i++){
                     IBreadcrumbHandler element = location.get(i);
                     if (element instanceof IRepoBreadcrumbHandler){
                        NodeRef nodeRef = ((IRepoBreadcrumbHandler)element).getNodeRef();
                        name = Repository.getNameForNode(this.getNodeService(), nodeRef);
                     }
                  }
              }
              return name;
        }

    At this time it's all and it works! Ugly and hacky solution, but it works (FF3). Maybe it'll be better to write own component (eg. some tree browsing component), but now I wanted some simple and fast (and really ugly). Oh, I almost forget – this thread helped me a lot!

    Playing with titlebar and navigator

    My next quest was to change content of titlebar and navigator box. Titlebar was really easy, but navigator box is 'hell itself', because it's r:navigator component and i didn't want to create another component just for adding one more line.

    My next quest was to change content of titlebar and navigator box. Titlebar was really easy, but navigator box is 'hell itself', because it's r:navigator component and i didn't want to create another component just for adding one more line.

    Titlebar editing is very easy. Final result is on following screenshot:

    Image and video hosting by TinyPic

    That topmenu is created by a styled a:modeList component with some listItems. ModeList has actionListener set and after listItem is clicked, its value is sent to that listener (which is a big switch). So my first task was to bridge that listener. Original code is placed in NavigationBean and that method is named toolbarLocationChanged. I wrote my own eventListener in 'someBean' this way:

    public void toolbarLocationChanged(ActionEvent event){
         UIModeList locationList = (UIModeList)event.getComponent();
         String location = locationList.getValue().toString();
         if(location.equals("archivace")){
          . . .
          }else this.navigator.processToolbarLocation(location, true);

    In this piece of code is loaded 'location' – value of listItem (ie companyhome, userhome, archivace… see later) and is compared with identificator of my action.If equals, something happens (see next listing ;-)), else original processToolbarLocation routine in NavigationBean is called.

    That something piece of code, which happens in that case is creation of breadcrumb and some informations in NavigationBean are changed, follows here:

    List<IBreadcrumbHandler> elements = new ArrayList<IBreadcrumbHandler>(1);
    Node archivace = new Node(getArchivaceRef());
    elements.add(new NavigationBreadcrumbHandler(archivace.getNodeRef(), archivace.getName()));
    this.navigator.setLocation(elements);
    this.navigator.setCurrentNodeId(archivace.getId());

    But that is not all, you have to inform registered beans that the current area has changed:

    UIContextService.getInstance(FacesContext.getCurrentInstance()).areaChanged();

    And because the current node is not added to the dispatch context automaticaly, it's needed to add it programaticaly (this was very old bug of Alfresco and in some cases still lasts – forum, jira):

     this.navigator.setupDispatchContext(this.navigator.getCurrentNode());

    Last thing to do is to force a navigation to refresh the browse screen breadcrumb:

    FacesContext context = FacesContext.getCurrentInstance();
     context.getApplication().getNavigationHandler().handleNavigation(context, null, "browse");

     That's all java coding. Now some jsp: you have to edit file titlebar.jsp in /jsp/parts/ and change actionListener in a:modeList and add another a:listItem as seen on next listing (simplified):

    <a:modeList . . . actionListener="#{SomeBean.toolbarLocationChanged}">
          <a:listItem value="archivace" label="Archivace" rendered="#{SomeBean.isArchivaceExist}"/>
    </a:modeList>

    Ah, I see there is evaluator – isArchivaceExist. It's just an evaluator, which returns true/false – if space I want to view exists, so that listItem is viewed just in case of that space exists.

     

    As I can see, it'll be better to move navigator hacks to next blogpost.

    Mimetype detection in upload component

    I posted short topic about my way to uploading files to Alfresco some time before. Now I turned all that code into my own component and added it to Tag Library and also added mimetype detection and some other amazing stuff. I'll try to describe some facts about my implementation in this blogpost, so be encouraged to keep reading ;-).

    I posted short topic about my way to uploading files to Alfresco some time before. Now I turned all that code into my own component and added it to Tag Library and also added mimetype detection and some other amazing stuff. I'll try to describe some facts about my implementation in this blogpost, so be encouraged to keep reading ;-).

    I'm not going to describe how to build own component, there are many tutorials on web (for example here or here) and it's much simplier than stuff I'm going to write about (many ughly javascript code). So let's begin.Firstly, thanks to component,jsp code is simplified. I compressed whole code into component (form inputs, labels, also js libraries includes), so my code lookslike this (note that javascript part is optional and is there just to disable OK button on page load; also mimetype selector is optional).

    <%@ taglib uri="http://www.shmoula.cz/jsf/component/tags" prefix="shmoula" %>

    <script type="text/javascript">
    function pageLoaded(){
        document.getElementById("dialog:finish-button").disabled = true;
    }
    window.onload = pageLoaded;
    </script>

    . . .

        <h:inputHidden id="fileId" value="#{SlowUpload.fileId}" />
        <shmoula:upload label="soubor" />
        <r:mimeTypeSelector id="mime-type" value="#{SlowUpload.mimeType}" />

    . . .

    FileId parameter does still the same thing – it connects get data with post data – identificator is created and then AJAX call is made, in which goes this id and filename to servelt, which creates a new FileBean and also get mimetype from filename and send it back. You can see this snippet on following listing.

     public void doGet(HttpServletRequest request,HttpServletResponse response)throws ServletException, IOException{
           PrintWriter out = response.getWriter();
           
           try{
               // creating a new bean for fileinfo storing and also setting some info
               this.fileUploadBean = new FileUploadBean();
               this.uploadId = request.getParameter("uploadid");
               String filename = request.getParameter("filename");

               // fetching mimetypeService from SlowUpload bean; more info follows
               SlowUpload slowUpload = (SlowUpload)request.getSession().getAttribute("SlowUpload");
               MimetypeService mimetypeService = slowUpload.getMimetypeService();
           
               // guessing and returning mimetype
               // if mimetype is not recognized, application/octet-stream is returned

               out.print(mimetypeService.guessMimetype(filename));
               
           }catch(Exception e){
               out.print("FALSE");
           }
    }

    MimetypeService must be injected into SlowUpload bean, so I need some Setters in my class for that and definition in Faces config:

     <managed-bean>
           <managed-bean-name>SlowUpload</managed-bean-name>
           <managed-bean-class>cz.shmoula.SlowUpload</managed-bean-class>
           <managed-bean-scope>session</managed-bean-scope>
    . . .
           <managed-property>
               <property-name>mimetypeService</property-name>
               <value>#{MimetypeService}</value>
           </managed-property>
    . . .
       </managed-bean>

     Now onComplete function is called thanks to prototype library AJAX calling. In this function a right option in select box is selected just through a simple loop, as you can see:

    function callbackFunction(originalRequest){
        var vystup = new String(originalRequest.responseText);
        
        if(vystup=="FALSE"){
            rollback(1);
            alert("Chyba pri uploadu");
        }else{
            var select = document.getElementById("dialog:dialog-body:mime-type");
            for(i=0;i<select.length;i++){
                var selectVal = new String(select.options[i].value);
                if(selectVal.match(vystup)!=null){
                    select.options[i].selected = true;
                    select.selectedIndex = i;    
                    break;
                }
            }
        }
    }

    And after all this whole formular is sent to servlet via POST method. All this is described in previous post, so you can check it out there. But there is one change – servlet returns success of operation inside hidden input. So I changed servlet this way:

    response.setContentType("text/html");
    PrintWriter out = response.getWriter();
    out.println(". . .<input type="hidden" id="iframe_status" value=""+this.fileUploadBean.getFileName()+"" />. . .");

    On client side is after (un-)successfully file upload called handle_upload() function, which gets value from included iFrame and decides that operation was or was not successfull and call appropriate actions:

    function handle_upload(){
        var doc = getIframeDocument();
        var filename = doc.getElementById("iframe_status").value;
       
        if(filename != "FALSE"){
            var fileNameInput = document.getElementById('dialog:dialog-body:fileName');
            if(fileNameInput!=null)
                fileNameInput.value = filename;
            checkButtonState();
        }else{
            rollback(1);
            alert("Chyba pri uploadu!");
        }
    }

    CheckButtonState function does nothing else, than enable OK button for complete submit all those information (if some other conditions came true) and rollback function sets form target to original target – …/dialog/container.jsp.

    That's all folks! Simplified procedure of my component activity. I wish I have not to write these hacks and all those things are accessible by developer in alfresco (they are there, but you can't use them outside specified containers or-how-to-name-that) some simple way. Maybe in future I hope!