lunes, 19 de diciembre de 2011

Rendering Paragraphs with AJAX, Part III: Autocomplete example

In Rendering Paragraphs with AJAX Part 1 and Part 2 I showed basic examples of using AJAX in paragraphs in Magnolia CMS. In this last post, I demonstrate how to create a predictive search for contacts and how to render a contact's details when it is selected.

The end result looks like this, hoping it motivates you to read the whole post :-). It is a new paragraph that contains a text box. You type a contact name in the box and the paragraph starts listing matching contacts from the directory.



When you select one of the contacts, a contact paragraph is rendered, displaying full contact information from the directory.


What is needed to achieve this? A paragraph, a model class and of course a nice jQuery Autocomplete widget. The last bit is what makes this example so simple to create.

Paragraph script

The paragraph script looks like this.
 [#assign cms=JspTaglibs["cms-taglib"]]  
 [#-- TODO This link and script MUST BE MOVED to a file into the theme --]    
 <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css"   
    rel="stylesheet" type="text/css"/>  
 <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.5/jquery.min.js">
    </script>
 <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js">
    </script>
 [#-- End TODO --]
 [#assign contentLink= mgnl.createLink(content)!]  
 <script>  
 jQuery(document).ready(function() {  
    jQuery("input#autocomplete").autocomplete({  
       source: function( request, response ) {  
          jQuery.getJSON('${contentLink}', {term : request.term}, function(data) {  
             response(data);  
             });  
          },  
       select: function( event, ui ) {  
          jQuery.get('${contentLink}', { uuid: ui.item.uuid},function(data) {  
          jQuery('#ajax').html(data);  
          });  
          return false;  
          }  
       });  
    });  
 </script>  
 [@cms.editBar /]  
 <div>  
    Find Contacts: <input id="autocomplete" />  
    <div id="ajax">  
    </div>  
 </div>  
Note that the links to jQuery UI cannot be placed directly in the script as I have done here. The code above is just a demonstration so you can see what files are needed. See jQuery Autocomplete documentation for details.

The paragraph script renders an input field that has an ID autocomplete. The source for this field is a JSON object provided by the paragraph model via an AJAX call. The JSON object provides contact names starting with the string you type in the input field. When a contact is selected, a second AJAX call asks the paragraph model to render a contact paragraph using the UUID of the selected item.

Autocomplete paragraph model class

The paragraph model class for autocomplete looks like this.
 public String execute() {  
    String uuid = MgnlContext.getParameter("uuid");  
    //first it will run the autocomplete  
    if(StringUtils.isEmpty(uuid)) {  
       return renderAutocomplete();  
    } else {  
       return renderParagraph(uuid);  
    }  
 }  
There are two method calls in the execute method:
  • renderAutocomplete returns a JSON object of contact names and UUIDs.
  • renderParagraph renders a contact paragraph for the selected UUID.

renderAutocomplete method

The renderAutocomplete method looks for the contact information by givenName and then constructs a JSON object with two fields, label and uuid, and sends it to the response.
 private String renderAutocomplete() {  
    String autocomplete = "";  
    String repositoryId = "data";  
    String dataType = "Contact";  
    String propertyName = "givenName";  
    String term = MgnlContext.getParameter("term");  
    if(StringUtils.isNotEmpty(term)){  
       String statement = "//element(*, "+ dataType +")[jcr:like(fn:upper-case(@"    
          + propertyName + "),'%" + term.toUpperCase() + "%')]";  
       Collection<Content> nodes = QueryUtil.query(repositoryId, statement , "xpath");    
       autocomplete +="[";  
       Iterator<Content> it = nodes.iterator();  
       while (it.hasNext()) {  
          Content node = it.next();  
          autocomplete += "{ \"label\":\"" + NodeDataUtil.getString(node, propertyName)   
          + " " + NodeDataUtil.getString(node, "familyName") + "\", \"uuid\": \"" 
          + node.getUUID() +"\"}";  
          if(it.hasNext()) {  
             autocomplete += ",";  
          }   
       }  
       autocomplete +="]";  
       try {  
          MgnlContext.getWebContext().getResponse().getOutputStream().print(autocomplete);  
          } catch (IOException e) {  
            // TODO Auto-generated catch block  
            e.printStackTrace();  
       }  
       return RenderingModel.SKIP_RENDERING;  
    }  
 return super.execute();  
 }  

renderParagraph method

The renderParagraph method is bit more complex because what we want to render is of type Contact. Instead of using the renderParagraph method from MagnoliaTemplatingUtilities, we have to render it using ParagraphRendererManager.
 private String renderParagraph(String uuid) {  
    String repositoryId = "data";  
    String paragraphName = "contact";  
    Content ajaxContent = ContentUtil.getContentByUUID(repositoryId, uuid);  
    if(ajaxContent != null) {  
       try {  
          if (ajaxContent != null && ajaxContent.isNodeType(ItemType.CONTENTNODE.getSystemName())) {  
             MagnoliaTemplatingUtilities.getInstance().renderParagraph(ajaxContent,   
                MgnlContext.getWebContext().getResponse().getWriter(), paragraphName);  
          } else if(ajaxContent != null && !ajaxContent.isNodeType(ItemType.CONTENTNODE.getSystemName())) {  
             //this bit of code is needed as content is not a contentnode, is a datatype from data repo.  
             Paragraph paragraph = ParagraphManager.getInstance().getParagraphDefinition(paragraphName);  
             ParagraphRenderer renderer = ParagraphRendererManager.getInstance().getRenderer(paragraph.getType());  
             renderer.render(ajaxContent, (Paragraph) paragraph, MgnlContext.getWebContext().getResponse().getWriter());  
          }  
       } catch (RenderException e) {  
          // TODO Auto-generated catch block  
          e.printStackTrace();  
       } catch (IOException e) {  
          // TODO Auto-generated catch block  
          e.printStackTrace();  
       }  
    }  
    return RenderingModel.SKIP_RENDERING;  
 }  

Contact paragraph model class

For the Contact paragraph, I just used the one provided by STK and changed the model to get the content from the UUID provided.
 public String execute() {  
    String uuid = MgnlContext.getParameter("uuid");  
    if(StringUtils.isEmpty(uuid)) {  
       uuid = getContent().getNodeData("contact").getString();  
    }  
    HierarchyManager hm = MgnlContext.getHierarchyManager(DataModule.getRepository());  
    if (!StringUtils.isEmpty(uuid)) {  
       try {  
          contact = hm.getContentByUUID(uuid);  
          } catch (RepositoryException e) {  
             throw new RuntimeException(e);  
       }  
    }   
    return super.execute();  
 }  
Download the code for this example from the Magnolia SVN. It is just a working sample to demonstrate this functionality and might be removed at some point so better grab it quick!