MITCO Solutions

Think Big, start Small

0 notes

Display multiple messages on a custom Share Action

I’m sure you’ve all developed a custom Share Action by taking an example from the share-extra’s Google Code project.

The first time I’ve made my own cool custom Action, I’ve used the Backup Action as an example.

Normally one can re-use all of the code, but in some cases this example isn’t sufficient. In my case I wanted to have a Notify Action which shows a user messaged that a notification has been added or been removed. So I couldn’t have 1 success message. I could use the failure message, but that wouldn’t be a nice implementation in case something really went wrong.

So I’ve changed my Action a bit to show multiple messages.

The default client JS Action looked like this:

Alfresco.doclib.Actions.prototype.onActionNotify = function DL_onActionNotify(file) {
      this.modules.actions.genericAction({
         success : {
            message : this.msg("message.notify.success", file.displayName),
            events : [ {
               name : "metadataRefresh"
            } ]
         },
         failure : {
            message : this.msg("message.notify.failure", file.displayName)
         },
         webscript : {
            name : "notify/site/{site}/{container}",
            method : Alfresco.util.Ajax.POST
         },
         params : {
            site : this.options.siteId,
            container : this.options.containerId
         },
         config : {
            requestContentType : Alfresco.util.Ajax.JSON,
            dataObj : {
               nodeRefs : [ file.nodeRef ]
            }
         }
      });
   };

So I needed something like:

         success1 : {
            message : this.msg("message.notify.success1", file.displayName),
            events : [ {
               name : "metadataRefresh"
            } ]
         }
success2 : {
            message : this.msg("message.notify.success2", file.displayName),
            events : [ {
               name : "metadataRefresh"
            } ]
         }
,
         failure : {
            message : this.msg("message.notify.failure", file.displayName)
         },

By default this isn’t possible, so instead using the success and failure handler I needed the real AJAX CallBackHandler.

After having contact with an Alfresco Engineer, who replied on my Forum Post. I’ve come up with the following code:

Alfresco.doclib.Actions.prototype.onActionNotify = function DL_onActionNotify(file) {
        this.modules.actions.genericAction({
            success : {
                events : [ {
                    name : “metadataRefresh”
                } ],
                callback: {
                    fn : function DL_oAN_success(data){
                        var resultJson = YAHOO.lang.JSON.parse(data.serverResponse.responseText);
                        var added = resultJson.results[0].added;
                        if (added)
                            Alfresco.util.PopupManager.displayMessage({
                                text: this.msg(“message.notify.added”, file.displayName)
                            });
                        else
                            Alfresco.util.PopupManager.displayMessage({
                                text: this.msg(“message.notify.removed”, file.displayName)
                            });
                    },
                    scope : this
                }
            },

So I’ve replaced the message block within the success event with the callback block. Now I’ve got full control of the CallBackHandler.

The message block eventually called the Alfresco.util.PopulManager.displayMessage function. So now you’ve got to do it directly.

So this fixes the client side, now we only have to push the new added property from the Repository webscript.

The default result object looks like this:

    var result = {
             nodeRef: null,
             action: “notifyAction”,
             success: true,
          }

So I’ve changed it to the following to get it to work:

    var result = {
             action: “notifyAction”,
             success: true,
             added: false
          }

I don’t need the nodeRef so I’ve deleted it and added my own property.

This opens a whole new world into Share for me. By using the CallBackHandler I can use any Repository property I want and evaluate on the Client Side to build my logic.

Filed under alfresco share action

22 notes

MITCO created an enhancement for Alfresco Share

When adding a node of type cm:folder to bpm_package (in e.g. a workflow) an incorrect URL gets generated through the template packageitems.ftl

In packageitems.ftl at line 5 the following happens:

<#local documentLinkResolver>
    function(item){
        return Alfresco.util.siteURL(“document-details?nodeRef=” + item.nodeRef, { site: item.site });
    }
</#local>

This should be:

<#local documentLinkResolver>
    function(item){
        if (item.isContainer)
            return Alfresco.util.siteURL(“folder-details?nodeRef=” + item.nodeRef, { site: item.site });
        else
            return Alfresco.util.siteURL(“document-details?nodeRef=” + item.nodeRef, { site: item.site });
    }
</#local>

I’ve tested it against Alfresco 3.4x and now when adding a cm:folder to the bpm_package a correct URL gets generated.

Filed under Alfresco Share MITCO MITCO Solutions Workflow

2 notes

Alfresco: How to access the Database for special querying, like retrieving the Unique Logins

Recently a client wanted something, which couldn’t be retrieved from the normal Alfresco API. The data was present in Alfresco, I did a DB query and got the desired results, but now I wanted to present that in a Dashlet.

After doing some research, I finally found the way to do it.

I created a Java-Backed Webscript, like stated at the Alfresco Wiki.

That is quite easily done, the next step was to know which DB-class I needed to pick. I know that Alfresco uses Hibernate, so making a separate JDBC-connection is just stupid.

After struggling with different classes, I eventually found the right one. I need to extend my class from org.springframework.orm.hibernate3.support.HibernateDaoSupport

One can use HQL Queries or plain SQL.

Beginning with plain SQL I ended up doing more translations and comparisons than using HQL. It was sometimes a bit hard to creating the right Query, because for testing purposes I directly queried the DB. So re-writing SQL to HQL is something which can take a while.

This is an example class which queries directly the DB:

public class DBQuery extends HibernateDaoSupport {

    public List<Object[]> getResultSet(final Date startDate, final Date endDate) {
        HibernateCallback callback = new HibernateCallback() {
            public Object doInHibernate(Session session) throws HibernateException, SQLException {
               
                Date startQuery = null;
                Date endQuery = new Date();;
               
                if (startDate == null){
                    startQuery = new Date();
                    Calendar cal = Calendar.getInstance();
                    cal.setTime(endQuery);
                    cal.add(Calendar.MONTH, -1);
                    startQuery = cal.getTime();
                } else
                    startQuery = startDate;
               
                if (endDate != null)
                    endQuery = endDate;
               
                StringBuilder query = new StringBuilder();
               
                query.append(“SELECT distinct date_format(aaf.date, ‘%d-%m-%Y’), aaf.userId “);
                query.append(“FROM org.alfresco.repo.audit.hibernate.AuditFactImpl aaf, org.alfresco.repo.audit.hibernate.AuditSourceImpl aas “);
                query.append(“WHERE aaf.auditSource = aas “)
                    .append(“AND aas.method = ‘authenticate’ “)
                    .append(“AND aas.service = ‘AuthenticationService’ “)
                    .append(“AND aaf.date BETWEEN :start AND :end “)
                    .append(“ORDER BY aaf.date DESC”);
               
                Query queryResult = session.createQuery(query.toString());
                queryResult.setParameter(“start”, startQuery);
                queryResult.setParameter(“end”, endQuery);
               
                return queryResult.list();
            }
        };

        List<Object[]> result = (List<Object[]>) (getHibernateTemplate().execute(callback));

        return result;
    }

What does this query do, it retrieves the Unique Logins through the AuthenticationService, which should be enabled in the AuditConfig-Custom.xml.

So you’ve got your results in an List<Object[]>, so how can we loop through these results?

The Object[] is an array containing the results from the specified columns. So Object[0] = aaf.date and Object[1] is aaf.userId.

Map<String, Object> map = new HashMap<String, Object>();

for (Object[] objects : result) {
    String date = Object[0];
    String userId = Object[1];
    //Do whatever you like with the results
    //Eventually put the results in a Map so they can be used in the Webscript/FTL

    map.put(“user”, objects[1]);
    map.put(“date”, objects[0]);
}

Full code of this Java-Backed Webscript:

public class RetreiveUniqueLogin extends DeclarativeWebScript {
   
    private DBQuery DBQuery;
   
    private SimpleDateFormat sf = new SimpleDateFormat(“dd MM yyyy”);

    protected Map<String, Object> executeImpl(WebScriptRequest req, WebScriptStatus status) {
       
        String start = req.getParameter(“startDate”);
        String end = req.getParameter(“endDate”);
       
        Date startDate = null;
        Date endDate = null;

        try {
            if (start != null && start.length() > 0)
                startDate = sf.parse(start);
            if (end != null && end.length() > 0)
                endDate = sf.parse(end);
        } catch (ParseException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
       
        List<Object[]> results = DBQuery.getResultSet(startDate, endDate);
        Map<String, Object> map = new HashMap<String, Object>();
       
        // loop through the results and send smart objects instead of a Object[]
        for (Object[] objects : results) {

            map.put(“user”, objects[1]);
            map.put(“date”, objects[0]);
        }
       
        Map<String, Object> model = new HashMap<String, Object>(7, 1.0f);
        model.put(“auditResults”, map);

        return model;
    }

  public DBQuery getDBQuery() {
        return DBQuery;
    }
    public void setDBQuery(DBQuery DBQuery) {
        this.DBQuery = DBQuery;
    }

And we need a Freemarker Template to present the results.

<table id=”auditPreviewTable” border=”0” class=”tablesorter”>
    <thead>
    <tr>
        <th>Date</th>
        <th>User</th>
    </tr>
    </thead>
    <#if auditResults??>
        <#list auditResults as result>
            <tr>
                <td>${result[“date”]}</td>
                <td>${result[“user”]}</td>
            </tr>
        </#list>
    </#if>
    </tbody>
</table>

And of course you need to register the Java-Backed Webscript in a context file.

!!This code is written for an Alfresco version 2.2.x, so there is a big chance some classes are changed, the Webscript class is definitely changed, but some Hibernate imports could also differ!!

Filed under alfresco Java MITCO MITCO Solutions WebScript Dashlet Database

1 note

Alfresco: Disable the next button in an advanced workflow according to a constraint

Normally the Alfresco constraints doesn’t work in an advanced workflow. This issue is stated here:https://issues.alfresco.com/jira/browse/ALF-1846

According to Alfresco it’s fixed in 3.4.2. But I have a work-around/fix for all the older implementations if it’s limited to 1 transition button.

We have a task model type:

<type name=”test:submitTask”>
            <title>Submit Task</title>
            <parent>bpm:workflowTask</parent>
            <properties>
                <property name=”test:constraintField”>
                    <type>d:text</type>
                    <mandatory>true</mandatory>
                    <constraints>
                        <constraint ref=”test:minCharConstraint” />
                    </constraints>
                </property>
            </properties>
        </type>

We have a constraint:

<constraint name=”test:minCharConstraint” type=”LENGTH”>
            <parameter name=”minLength”>
                <value>3</value>
            </parameter>
            <parameter name=”maxLength”>
                <value>255</value>
            </parameter>
        </constraint>

We have a workflow transition:

<transition name=”Next” to=”Next Info”>
           <action class=”org.alfresco.repo.workflow.jbpm.AlfrescoJavaScript”>
                    <script>
                        //Do Something or Don’t :)
                    </script>
                </action>
        </transition>

We have a webclient-config-custom:

<config evaluator=”node-type” condition=”test:submitTask”>
        <property-sheet>
            <show-property name=”test:constraintField”/>
        </property-sheet>
    </config>

This is very basic when you model with advanced workflow. So now we need to be able to constrain this “test:constraintField” so that the minimum characters should be 3.

Now if we test this, the check will be done on save changes, but not on the “Next” transition button.

To get this to work, we need to create a component generator and make some few adjustments.

Because it’s a text field, we just need to extend the TextFieldGenerator.

I’ve made the generator globally as you can see below:

public class MinCharGenerator extends TextFieldGenerator {

    private String nextTransition;

    private final String PREFIX_TRANSITION = “transition_”;

    @Override
    @SuppressWarnings(“unchecked”)
    protected void setupConstraints(FacesContext context, UIPropertySheet propertySheet, PropertySheetItem property,
            PropertyDefinition propertyDef, UIComponent component) {

        if (nextTransition != null && nextTransition.length() > 0)
            propertySheet.setNextButtonId(PREFIX_TRANSITION + nextTransition);

        if (propertySheet.inEditMode() && propertySheet.isValidationEnabled() && propertyDef != null) {
            List<ConstraintDefinition> constraints = propertyDef.getConstraints();
            for (ConstraintDefinition constraintDef : constraints) {
                Constraint constraint = constraintDef.getConstraint();

                if (constraint instanceof RegexConstraint) {
                    setupRegexConstraint(context, propertySheet, property, component, (RegexConstraint) constraint, true);
                } else if (constraint instanceof StringLengthConstraint) {
                    setupStringLengthConstraint(context, propertySheet, property, component, (StringLengthConstraint) constraint, true);
                } else if (constraint instanceof NumericRangeConstraint) {
                    setupNumericRangeConstraint(context, propertySheet, property, component, (NumericRangeConstraint) constraint, true);
                }
            }
        }
    }

    public String getNextTransition() {
        return nextTransition;
    }

    public void setNextTransition(String nextTransition) {
        this.nextTransition = nextTransition;
    }

Let’s look at the method setupStringLengthConstraint(..).

The important part of this, is just setting the boolean from false to true. The method is described in the JavaDoc:

/**
    * Sets up a default validation rule for the string length constraint
    *
    * @param context FacesContext
    * @param propertySheet The property sheet to add the validation rule to
    * @param property The property being generated
    * @param component The component representing the property
    * @param constraint The constraint to setup
    * @param realTimeChecking true to make the client validate as the user types
    */

So we just enabled the realTimeChecking, which is set to false by default (I don’t know why, but it’s lame). I mean the JavaScripts methods are there to validate directly, then why don’t use them???

Now if you just deploy this ComponentGenerator and enable it in the webclient-config-custom then you’ll see that the saveChanges button is disabled by default and will get enabled after the 3rd character.

But we also want to enable this validation on our Transition button. So i’ve added a parameter to our Generator to know which is the transition button.

Let’s look at the faces-config-custom.xml.

<managed-bean>
        <!— Component which checks if the minimum characters are filled in —>
        <managed-bean-name>
            MinCharGenerator
        </managed-bean-name>
        <managed-bean-class>
            package.MinCharGenerator
        </managed-bean-class>
        <managed-bean-scope>request</managed-bean-scope>
        <managed-property>
            <property-name>nextTransition</property-name>
            <value>Next</value>
        </managed-property>
    </managed-bean>

The value in here, is the Transition name in the advanced workflow.

if (nextTransition != null && nextTransition.length() > 0)
            propertySheet.setNextButtonId(PREFIX_TRANSITION + nextTransition);

The code line above, sets the transition button as a wizard next button. This won’t make sense directly, but if you look at the Javascript which is created in the class UIPropertySheet in method renderValidationScript(…).

You’ll see that by default is disables the next & finish button. The finish button —> is the “Save Changes” button and there is no next button assigned in an advanced worfklow. The next button is mostly assigned in the default wizards.

Ofcourse you could rewrite the private renderValidationScript method, but then you’re rewriting alfresco core code. With this implementation it’s just an extension to the default code.

Have fun with it and if you have any questions just contact me through my website MITCO.