This is a multipart article in which I will discuss how to populate web form data dynamically. In this example we will be populating two pieces of metadata based upon the value of a third piece of metadata on a custom content type. The use case could look like my example, looking up product information by product number to capture common metadata applied to product related information such as marketing brochures, design specifications, and CAD drawings. I will be building this example based upon my Alfresco Repository Customization - Sample Eclipse Project and Alfresco Share Customization – Sample Eclipse Project, check out the articles if you want to learn more about how to set up an Eclipse project to extend Alfresco.

Overview

In order to populate web form data dynamically, we will need to make a lot of small extensions to Alfresco. The high level overview is that we will use a Share datalist to keep a lookup table for our form data. We will then create a repository webscript and a Share webscript to retrieve the information from the datalist. Then we will write a form validation handler to populate the web form as we type. In order to do this we will create a custom content model to create a content type for products and a datalist to hold product lookup information. In this article, we will cover only the repository customizations which include the custom content model and the repository webscript.

Custom Content Model

I am not going to go into extreme detail about content modeling as there are already many sources of information about content modeling such as these: Alfresco Wiki - Data_Dictionary_Guide, Alfresco Wiki - Step-By-Step:_Creating_A_Custom_Model, andecmarchitect.com - working with a custom content model. We will need to add two new types, the first is a datalist with product information and the second is a product information type. Most likely you would want to implement the second product information type as an aspect that you could apply to any content, but it really depends on your use case. I implemented it as a type for simplicity. The model file is located in /config/alfresco/extension/model and is calleddemoModel.xml. I am going to skip explaining everything but the type definitions from the file. Let's take a look at our first type:
<type name="dm:product"> <title>Demo Product</title> <parent>cm:content</parent> <properties> <property name="dm:productNumber"> <title>Product Number</title> <type>d:text</type> </property> <property name="dm:productName"> <title>Product Name</title> <type>d:text</type> </property> <property name="dm:productManufacturer"> <title>Product Manufacturer</title> <type>d:text</type> </property> </properties> </type>
As you can see this is a very simple type with three properties, product number, product name, and product manufacturer. They are all simple text properties buy you could easily make them any other type. Here is the datalist type:
<type name="dm:productList"> <title>Product List</title> <parent>dl:dataListItem</parent> <properties> <property name="dm:productListNumber"> <title>Product Number</title> <type>d:text</type> </property> <property name="dm:productListName"> <title>Product Name</title> <type>d:text</type> </property> <property name="dm:productListManufacturer"> <title>Product Manufacturer</title> <type>d:text</type> </property> </properties> </type>
A datalist must inherit from dl:dataListItem in order to be treated as a datalist by Share. The remainder of the type is pretty similar to any other type. Note that we need to use a different name for our properties in order to avoid namespace conflicts. The two types will be holding the same product information and we will essentially be copying from the datalist to the product. You can get a lot more information about implementing a custom datalist atecmarchitect.com - Custom Data Lists. We need to load the new content model. In order to do this we have created a file in /config/alfresco/extension called demo-model-context.xml. This is an ordinary spring context file and Alfresco processes any file ending in "context.xml". Here is our file in its entirety:
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE beans PUBLIC '-//SPRING/DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> <beans> <bean id="demo.extension.dictionaryBootstrap" parent="dictionaryModelBootstrap" depends-on="dictionaryBootstrap"> <property name="models"> <list> <value>alfresco/extension/model/demoModel.xml</value> </list> </property> </bean> </beans>
The important thing to take note of is the value element which points to our new content model file.

Webscript to Return Product Info

I am not going to go into too much detail on the webscript since there are lots of references already. My new webscript will simply take a single argument for the product number, run a Lucene search query for a product by using a property search based on the product number argument. I am returning the first matching element because I am making the assumption that there will not be duplicate product numbers in our lookup table datalist. The files are located in/config/alfresco/extension/templates/webscripts/com/tribloom/demo/product and the files that make up the webscript are the descriptor, product.get.desc.xml, the controller,product.get.js, and the view, product.get.json.ftl. With a few minor tweaks you could change this around to fit your exact needs. Here is the webscript descriptor, product.get.desc.xml:
 <webscript> <shortname>Get Product</shortname> <description>Lookup Product by Product Number</description> <family>Tribloom</family> <url>/tribloom/product/{number}</url> <format default="json">any</format> <authentication>user</authentication> </webscript>
There are just a few things to note here. First, it is a good practice to add a family to your webscripts to keep them together logically and make them easy to find. Use whatever grouping makes sense, I could just have easily used "demo" as my family. Second, I used a URL based argument rather than a query based argument for the product number. We will take a look at the code for the controller to see how this is done. Third, I have made the default format JSON for the response. That means that if I call the webscript without a file extension, JSON will be returned. Finally, the authentication is "user" so that any user can access the information. Keep in mind that this means the datalist needs to be accessible by any user, if they can't see it, it won't be returned. Now let's look at the controller, product.get.js:
var number; // Get the product number from the URL // If no product number is not given return an error if (url.templateArgs.number !== null) { number = url.templateArgs.number; } else { status.code = 400; status.message = "A product number must be provided."; status.redirect = true; } // Run a lucene search for the productListNumber // property equal to the given product number var query = "@dm\\:productListNumber:" + number; var result = search.luceneSearch(query); if (result.length != 0) { // always take the first response, there should not // be duplicates model.product = result[0]; }
The first thing I do is check to be sure that an argument was passed in. If there isn't then we return an HTML 400 error. As you can see I used url.templateArgs.number rather than a query string parameter that I might access using args.number to retrieve the argument from the URL. When I call the webscript it will look like this: http://localhost:8080/alfresco/s/tribloom/product/12345. If you haven't seen it before the "s" is shorthand for "service" in the URL. The Lucene search simply looks for any content with a productListNumber property with a value of the number parameter passed to the webscript. Now for a look at the view, product.get.json.ftl:
<#if product??> { "number":"${product.properties["dm:productListNumber"]}", "name": "${product.properties["dm:productListName"]}", "manufacturer": "${product.properties["dm:productListManufacturer"]}", } <#else> {} </#if>
This view is very simple, we are returning a JSON object with the product number, name and manufacturer. If no product is returned then we return an empty object {}.

Wrap Up

Since this project is about a Share customization I did not bother to expose any of the types in the custom content model in Alfresco Explorer. I also did not provide internationalization messages for any of the properties. You may or may not want to do this depending on if Alfresco Explorer will be used in your implementation. We will expose the new product content type through the Share interface in the next article.

Here is a link to the sample Alfresco Repository Customization Eclipse project for populating form data dynamically in Share forms. Remember this is incomplete until the next article which will cover the Share customizations. You will need to download the sample Alfresco Share Eclipse project for a complete example.

Demo-Repository-2


Loading Conversation