Archive for the ‘jsf’ Category

Dynamic Data-Driven Drop Downs for List Properties

Friday, August 8th, 2008

Dynamic Dropdowns

A customer request that comes up often is a need to create a property that is dynamically populated through drop down list. Alfresco has ability to set a pre-set list of drop-downs (i.e. apple, orange) through the LIST constraint, but there is no way to have list change dynamically.

For more on constraints, see this wiki page: http://wiki.alfresco.com/wiki/Constraints. Also, this post assumes some understanding of Component Generators – they are UI components that get dynamically rendered in the Document / Space details Property Sheet. See http://wiki.alfresco.com/wiki/Component_Generator_Framework.The code accompanying this article can be found on Community Developer Toolbox here.

There are several approaches to doing this, the first one is using the existing infrastructure and extend the List Constraints, and the second is to create your own component generator from scratch that generates the drop down the any way you like.
In this post, I’ll talk about reusing the existing list infrastructure. I built a custom constraint that receives a Lucene query and renders the drop down based on results of that query.

This allows you to model a node hierarchy somewhere and point to that hierarchy through the constraint. You can then maintain that hierarchy, adding or deleting nodes, which in turn will modify your drop down appropriately. For example, you can put nodes as a set of subspaces in the data dictionary, or use categories. A potentially useful artifact of this implementation is security filtering – if you choose to secure your nodes, you can have the drop downs render different for different people, based on what they have access to.

Creating SearchBasedListConstraint

The way list drop down works is that the TextFieldGenerator component generator has explicit code to detect the LIST constraint, and renders itself appropriately. It calls getAllowedValues method on the constraint to get the values the drop down should be populated with. I extended this ListConstraint, since if I implement the getAllowedValues to be dynamically, then the drop down should be dynamic.

In my implementation, I chose to separate the generic dynamic list capability from the Lucene specific capability. I have a class called SearchBasedListConstraint that extends ListOfValuesConstraint, and has an abstract method getSearchResult, which returns the list that will be passed on as AllowedValues. Implement this method in a subclass to be backed by anything you want, a file, a database, etc. Another thing i added
here is a reference to ServiceRegistry, so that Alfresco Repository Services could be called, such as SearchService, or NodeService, by subclases. There are a few other methods, which I will discuss in a later post.

public abstract class SearchBasedListConstraint extends ListOfValuesConstraint
{

@Override
public List<String> getAllowedValues()
{
List<String> allowedValues = getSearchResult();
super.setAllowedValues(allowedValues);
return allowedValues;
}

protected abstract List<String> getSearchResult() ;

There is a good forum post that talks a bit about implementing this as well.

http://forums.alfresco.com/en/viewtopic.php?f=4&t=11687

Dynamic DropDowns based on Lucene Searches

As far as an example implementation, I thought that allowing getSearchResult to be backed by a Lucene Query would be useful, since then you can store your data inside Alfresco. It also means that you can have dynamic querying based on content already in Alfresco, another potentially useful feature. So I subclass SearchBasedListconstraint with LuceneSearchBasedListConstraint. I’d like this to be parametrized and set from the content model (this is where the constraints are set), so I expose setQuery public method. Because the SearchService API requires a StoreRef to be passed, i introduce another setter called setStoreRef. I already have a reference to ServiceRegistry from superclass, so I can just start using it.

We are now ready to perform the search, the code currently is hardcoded to return the Name property.

protected List<String> getSearchResult()
{
if (logger.isDebugEnabled())
logger.debug(“Original Query ” + query);

StoreRef storeRef = new StoreRef(strStoreRef);
ResultSet resultSet = getServiceRegistry().getSearchService().query(storeRef, SearchService.LANGUAGE_LUCENE, finalQuery);
NodeService nodeSvc = getServiceRegistry().getNodeService();

List<String> allowedValues = new ArrayList<String>();
for (ResultSetRow row : resultSet)
{
allowedValues.add((String)nodeSvc.getProperty(row.getNodeRef(), ContentModel.PROP_NAME));
}

return allowedValues;
}

That’s all it takes. in the sample, there is some additional code to manage dependencies between dropdowns, which once again I will explain later.

Configuring Constraints

I am using the exampleModel that gets supplied with Alfresco in the extension directory, and added a couple of new fields – country and city. Here is the definition of constraint, note the parameter type query being set.

<constraint name=”my:customConstraint”
type=”org.alfresco.sample.constraints.LuceneSearchBasedListConstraint” >
<parameter name=”query”>
<value> TYPE:”{http://www.alfresco.org/model/content/1.0}content”
</value>
</parameter>
</constraint>

And now introduce this constraint to a property.

<property name=”my:country”>
<title>Country</title>
<type>d:text</type>
<index enabled=”true”>
<atomic>true</atomic>
<stored>false</stored>
<tokenised>false</tokenised>
</index>
<constraints>
<constraint ref=”my:customConstraint” />
</constraints>
</property>

Bootstrapping Alfresco Node Services

Here we have a bit of an issue. The constraints code does not by default get injected through Spring framework, it in fact gets dynamically instantiated through reflection. However, we need to use Alfresco’s foundation services in it, such as NodeService and SearchService, so we need a reference either to them, or to the ServiceRegistry. The solution is to initialize the Constraint from Spring through a use of static internal variables. Then you can use spring to inject ServiceRegistry to the setter, which in turn sets the internal static variable. From then any instances of the class will have access to the static variable.

Here is the code:

private static ServiceRegistry registry;

public ServiceRegistry getServiceRegistry()
{
return registry;
}

public void setServiceRegistry(ServiceRegistry registry)
{
SearchBasedListConstraint.registry = registry;
}

And now we can configure this, so that the Spring configuratoin takes care of setting the static variable.

<bean id=”LuceneSearchBasedListConstraintInitializer”
class=”org.alfresco.sample.constraints.LuceneSearchBasedListConstraint”>

<property name=”serviceRegistry”>
<ref bean=”ServiceRegistry”/>
</property>
</bean>

And the corresponding configuration:

<bean id=”LuceneSearchBasedListConstraintInitializer”
class=”org.alfresco.sample.constraints.LuceneSearchBasedListConstraint”>

<property name=”serviceRegistry”>
<ref bean=”ServiceRegistry”/>
</property>
</bean>

This should do it.

An even more interesting question is how to create dependencies between properties, i.e. so you can choose a country, and then cities of that country get populated automatically. This is part of the sample I provided, but will be explained in a future post.

Update 05/03/2009 – The Source code is now on the new share site (share.alfresco.com), in Developer Toolbox here.

Blogged with the Flock Browser


Alfresco Home | Legal | Privacy | Accessibility | Site Map | RSS  RSS

© 2012 Alfresco Software, Inc. All Rights Reserved.