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"]]   
 [#include "/autocomplete/paragraphs/macros/ajax.ftl"]  
 [#-- 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!

martes 29 de noviembre de 2011

Rendering Paragraphs with AJAX, Part II

In Rendering Paragraphs with AJAX Part 1, I showed a basic example of an AJAX paragraph in Magnolia CMS. We loaded an paragraph via an AJAX call and rendered it on the page.

Now we will see how to send parameters on the AJAX call and display changes by using the model class.

For a use case, suppose you have to display a large amount of content. To make the content easier for the user to browse, you add pagination. You only want to update the paginated content when the user clicks a Previous or Next button.

You need a way to tell the server what page the user is currently on. The server can then provide the previous page or the next page. In order to do this you need a model class, a paragraph definition and a Freemarker template script (.ftl file).

Template script

Add the following code to your Freemarker template script:

<div id="ajax">
[#assign pageIndex = model.pageIndex]
<script>
function next() {
    jQuery.get('${ctx.contextPath}${content.@handle}.html', 
{index: '${pageIndex}'}, 
 function(data) {
         jQuery('#ajax').html(data);
     });  
}
</script>
<div>
   Page: <span id="text">${pageIndex}</span>
</div>
<a href="javascript:next();">NEXT</a>
</div>
The content paragraph to be rendered is inside the div element that has an ID ajax. The script assigns the value that comes from the model method call to a variable pageIndex. In the script, an AJAX call reloads the current paragraph, passing a new parameter index with the value we assigned earlier.

In a nested div element, we write the current value of the variable on the page.

On the last line, we create a NEXT link that the user can click. The click will execute the AJAX call, run the paragraph model, and return the new pageIndex value.

Model class

Now let's see what the model class looks like.
public String execute() {
        
    String index = MgnlContext.getParameter("index");
    if(StringUtils.isNotEmpty(index)) {
        pageIndex = Integer.parseInt(index) +1;
    }
    return super.execute();
}

public int getPageIndex() {
    return pageIndex;
}
The class has two methods:
  • Overridden execute method that reads the parameter we sent by clicking NEXT and adds 1 to pageIndex.
  • getPageIndex method to retrieve the value of the member variable pageIndex.

Paragraph definition



The rendered NEXT  button looks like this on the page.

jueves 27 de octubre de 2011

Rendering Paragraphs with AJAX, Part I

Ever wondered how to reload just some paragraphs on your page? Imagine you have pagination and you want to reload just the paginated paragraph when clicking previous/next? Is Magnolia CMS capable of this without complex coding? Yes, and on this post you will find some ways of achieving this.

First thing to know is, since Magnolia CMS version 4.x paragraphs are autorenderable. What does this mean? It means you can copy the path of a paragraph, paste it in the address bar of your browser and you will see the paragraph rendered. Try the following URL from our demo site, it will render the teaser of the Article page.
http://demo.magnolia-cms.com/demo-project/main/00.html

What can you do with this? It means you don't need to create a servlet or anything to render a paragraph dynamically on a page. Just a call to the appropriate script will let you load any paragraph on our page.

Here is a sample using JQuery. Add this code into a .ftl file. You also need to create a paragraph definition and add it to be used in a template.
<script type="text/javascript">
function loadArticleTeaser() {
  jQuery.get('${contextPath}/demo-project/main/00.html',function(data) {
    jQuery('#ajax').html(data);
  });
}
</script>
<div id="ajax"> </div>
<a href="javascript:loadArticleTeaser()">Load Teaser via AJAX call </a>
The JQuery call (http://api.jquery.com/jQuery.get/) will get the specified URL and, on success, will run the method that loads the result (paragraph) from the AJAX call into a div with ID "ajax". To run the whole thing you just need to click the "Load Teaser via AJAX call" link.

This is a basic sample of an AJAX paragraph. In the next post you will see how to send parameters and process them in the paragraph model class.

martes 27 de septiembre de 2011

Tips & Tricks: Make activation comment mandatory

With this tip, you can make everybody to write a comment when they send an activation request, though I cannot guarantee that comment would be meaningful!

Step 1. Set property required=true in activation dialog.



Step 2. Add the following code to the saveOnClick property of the dialog.

if(document.getElementById('comment').value != '')  {window.close();opener.mgnl.workflow.WorkflowWebsiteTree.submitActivation($('mgnlFormMain'));} else {alert('Must write a comment for activation.');}

lunes 29 de agosto de 2011

Creating site navigation from data nodes

This solution has been implemented for Magnolia Shop Module (contributed by fastforward).

The aim is to create menu navigation based on nodes in the data repository. The navigation should be displayed on an existing site as section navigation. This way, when you navigate to the section, product categories defined in data repository will be used as items in the vertical menu.

Note that the breadcrumb is also updated with the product categories.

Assuming we know how the default navigation works in STK, we do the following:
  1. Create customized navigation classes. One class will provide the navigation model and the second will define the navigation item that provides us with the item title and URL.

    See ProductCategoryNavigationModel and ProductCategoryNavigationItem as examples. The getItems method returns the relevant nodes from the data repository. Constructor parameters for both classes depend on what you need for your implementation. In this particular case the parameter is specific to the shop, the shop name, so that we can create a correct link from the data repository.
  1. Create a template model class that instantiates the customized navigation and assigns the model class to our template. Doing it this way, you keep the default navigation for all site content that is not part of the shop.

    See ShopSingletonParagraphTemplateModel where the navigation is instantiated.

    public ProductCategoryNavigationModel getProductCategoryNavigation() {return new ProductCategoryNavigationModel(getCurrentShop());}
    As we are also customizing the breadcrumb, look at the getBreadcrumb method as well where items from the data repository are appended.

    Once we have the model class ready, we add it to our template configuration

  1. Freemarker templates.

    We optionally need a new verticalNavigation.ftl.
    The only change in vertical navigation is to call our custom navigation that we created with a new name:
    [@renderNavigation navigation=model.productCategoryNavigation /]
    And we set the template file in the site configuration of our custom template definition:

martes 12 de julio de 2011

Adding extra styles files to an existing theme

The question raised when creating a new module: how can I add the styles files of my module to the pop theme?

This guide shows how to add the installation task and how to bootstrap the content, but if you don't want to code anything, at the end of the blog there is an explanation on how to do it directly on the author instance:

Step 1, Create the new styles file i.e. myextrastyles.css

Step 2, Put the file in the module resources folder following STK theme structure, src/main/resources and the path shown in the following screenshoot:




Step 3, Add an installation task on your module versionhandler.

protected List getExtraInstallTasks(InstallContext installContext) {
final List installTasks = new ArrayList();
installTasks.addAll(super.getExtraInstallTasks(installContext));
installTasks.add(new InstallResourcesTask("/templating-kit/themes/pop/css/mystyles.css", 
   "processedCss", STKResourceModel.class.getName()));


Step 4, Update the theme configuration. In order for the theme to find the new style file, the file has to be configured in the theme configuration.




Step 5, Export the configuration of our file shown in Step 4. Once exported you can place the file (config.modules.standard-templating-kit.config.themes.pop.cssFiles.myextrastyles.xml)
in the bootstrap folder of your module.

NOTE: All this can be achieved without any code, you just need to set the file in the theme configuration (Step 4) and upload this new styles file into the resources workspace.

jueves 10 de marzo de 2011

Adding Flickr images to Magnolia CMS

This is a step-by-step guide on how to create your custom DAM and plug it into Magnolia CMS. Will allow you to add images from external services like Flickr, as well as adding images from other servers using their url (It comes with a forge module!)

This is a sample handle for external image/video urls, very basic as it does not add the imaging support for now.

* Requirements - Standard Templating Kit Module

1. The Asset

We need to create our own Asset class that will implement the interface Asset

If we want to have exact same behavior as in STK/ETK we will need to find a way to store 'meta data' (caption, description) of our assets, we could use the data module for that matter.

In this sample we wont store any metadata for the images, nor use the imaging support.

That leaves us with just one method to implement 'getLink' and this will be the value stored when we open the dialog to select an image.

public String getLink() throws DAMException {
return link;
}

Creation of link is on the class constructor, as easy as this

link = nodeData.getString();



2. The handler.

Should extend class AbstractHandler . We will extend from AbstractHandler that has the methods to read the configuration, that leaves us with two methods to implement as needed: getAsset and getAssetByKey. In this case we implement getAsset to return an instance of our Asset.

final String name = getNodeDataName(node, nodeDataPrefix);

Important thing to note here is that we need to get the name of the nodeData that has the url of our image. When you set an image using the dam controls, you name your node as i.e. 'myimage' and on saving you will get a nodeData with the name you gave plus the name of the dam control used. To make it simpler a call to the method getNodeDataName will give you the real name with which your node has been saved. In this sample it will contain the exact url of our image, no need of further processing.

final NodeData nodeData = node.getNodeData(name);

This line above gets the node data, now with the right name.

return new UrlAsset(nodeData);

Here is where we return our asset.

3. The controls (configuration).

Need to add the configuration for this external dam. For that we create a new node under our site configuration. There we put the handler we are going to use and the control/s in which we will store the image information. See image:




And to finish this post there is a module in Magnolia forge magnolia-module-urldam with the code to add an external dam handler.