Actions are special components provided by the Backoffice Framework that you can use inside your widgets. They are used to trigger an action, depending on the state of widget model property. As we will see in our example that out action button is enabled when a certain property is true in our item.

This is a simple guide to creating and adding an action in our widget. There is a lot more we can do and configure, for more information, view the documentation.

One of our client’s requirement was to send emails to B2B Customers that are disabled, so that the Customers can register and enable their account. To do this, the first step was to be able to select multiple B2B Customers at a time so that we can send registration invitation to multiple Customers at a time. I have written about How to Make Search Result Widget in Backoffice Multi-Select, view that if you need to how to do that.

In this post, we will create a new button, that we will use to send registration emails to the selected Customers that are dsabled.

Here is the requirements:

  1. Create an action button that is only enabled when the selected B2B Customer(s) are disabled.
    1. Publish an email event for the disabled Customers.
  2. Add the action button to the List Vew Action of B2B Customer.

Creating the Action Button Definition

 Here are the steps:

Create a definition.xml file in the following directory:

<NAME_OF_EXTENSION>/backoffice/resources/widgets/actions/<NAME_OF_ACTION>

Here is my definition.xml:

<action-definition id="com.hybris.cockpiting.action.sendregistrationinvite"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:noNamespaceSchemaLocation="http://www.hybris.com/schema/cockpitng/action-definition.xsd">

	<name>Send Registration Invite</name>
	<description>Send Registration Invite to the disabled customers</description>
	<author>Me</author>
	<version>0.1</version>

	<actionClassName>com.myextension.actions.SendRegistrationInviteAction</actionClassName>

	<inputType>java.lang.Object</inputType>
	<outputType>java.lang.Object</outputType>

	<iconUri>icons/send_registration_invite.png</iconUri>
	<iconDisabledUri>icons/send_registration_invite_disabled.png</iconDisabledUri>
</action-definition>


The actionClassName tag defines the implementation class. inputType and outputType tags are optional. The respective icons will be used depending upon whether the icon is disabled or enabled. Know that the images must be stored in the following directory: <NAME_OF_EXTENSION>/backoffice/resources/widgets/actions/<NAME_OF_ACTION>

Creating the Action Implementation Class

One thing to note is that Actions do NOT have access to the Spring application context. Therefore, if we need to inject Spring bean into our actioon, we can use the @Resource annotation.

Our implementation class must implement CockpitAction<I, O> interface. Therefore, there is only one method that we must implement (the perform(ActionContext<I>) method, the rest have default implementations), but in our example, we will override all of the methods.

 This is the snippet of the implementation:

package com.myextenstion.actions;

import com.myproject.core.event.RegistrationInviteEvent;
import com.hybris.cockpitng.actions.ActionContext;
import com.hybris.cockpitng.actions.ActionResult;
import com.hybris.cockpitng.actions.CockpitAction;
import de.hybris.platform.b2b.model.B2BCustomerModel;
import de.hybris.platform.commerceservices.event.AbstractCommerceUserEvent;
import de.hybris.platform.commerceservices.security.SecureToken;
import de.hybris.platform.commerceservices.security.SecureTokenService;
import de.hybris.platform.core.model.user.CustomerModel;
import de.hybris.platform.servicelayer.event.EventService;
import de.hybris.platform.servicelayer.i18n.CommonI18NService;
import de.hybris.platform.servicelayer.model.ModelService;
import de.hybris.platform.site.BaseSiteService;
import de.hybris.platform.store.services.BaseStoreService;
import org.zkoss.zul.Messagebox;

import javax.annotation.Resource;
import java.util.HashSet;

public class SendRegistrationInviteAction implements CockpitAction<Object, Object> {

    @Resource
    SecureTokenService secureToken;

    @Resource(name = "modelService")
    private ModelService modelService;

    @Resource
    private EventService eventService;

    @Resource
    private BaseStoreService baseStoreService;

    @Resource
    private BaseSiteService baseSiteService;

    @Resource
    private CommonI18NService commonI18NService;

    @Override
    public ActionResult<Object> perform(ActionContext<Object> actionContext) {
        HashSet<Object> dataList = (HashSet<Object>) actionContext.getData();

        if(dataList.iterator().next() instanceof B2BCustomerModel) {
           for (Object o : dataList) {
               B2BCustomerModel b2BCustomerModel = (B2BCustomerModel) o;

               if(b2BCustomerModel.isLoginDisabled()) {
                   final SecureToken token = new SecureToken(b2BCustomerModel.getUid(), System.currentTimeMillis());
                   final String encryptedToken = secureToken.encryptData(token);

                   b2BCustomerModel.setToken(encryptedToken);

                   modelService.save(b2BCustomerModel);
                   eventService.publishEvent(initializeEvent(new RegistrationInviteEvent(encryptedToken), b2BCustomerModel));
               }   
           }
           Messagebox.show(actionContext.getLabel("action.send.registration.invite.sent"),
               actionContext.getLabel("action.send.registration.invite.sent.title"), Messagebox.OK, Messagebox.INFORMATION);
           return new ActionResult<Object>(ActionResult.SUCCESS);
        }   

        Messagebox.show(dataList + " (" + ActionResult.ERROR + ")",
            actionContext.getLabel("action.send.registration.invite.sent.title"), Messagebox.OK, Messagebox.ERROR);
        return new ActionResult<Object>(ActionResult.ERROR);
    }

    @Override
    public boolean canPerform(ActionContext<Object> ctx) {
        HashSet<Object> dataList = (HashSet<Object>) ctx.getData();
        if(dataList != null) {
            boolean canPerform = false;
            for (Object o : dataList) {
                B2BCustomerModel b2bCustomer = (B2BCustomerModel) o;
                if(b2bCustomer.isLoginDisabled()) {
                    canPerform = true;

                    break;
                }
            }
            return canPerform && dataList.iterator().next() instanceof B2BCustomerModel;
        }

        return false;
    }

    @Override
    public boolean needsConfirmation(ActionContext<Object> ctx) {
        return true;
    }

    @Override
    public String getConfirmationMessage(ActionContext<Object> ctx) {
        return ctx.getLabel("action.send.registration.invite.confirm");
    }

    protected AbstractCommerceUserEvent initializeEvent(final AbstractCommerceUserEvent event, final CustomerModel customerModel) {

        event.setBaseStore(baseStoreService.getCurrentBaseStore());
        // Get the (first) site this eay because we are publishing this event from the Backoffice and Backoffice does NOT have a 'Single Current' site it is associated with
        event.setSite(baseSiteService.getAllBaseSites().iterator().next());
        event.setCustomer(customerModel);
        event.setLanguage(commonI18NService.getCurrentLanguage());
        event.setCurrency(commonI18NService.getCurrentCurrency());

        return event;
    }
}

We have added localized strings for the Messagebox. So, if you want to localize an action to display some labels in different languages, we need to define these labels in the relevant path. In the directory myextension/backoffice/resources/widgets/actions/<NAME_OF_ACTION>, create labels folder with labels.properties and labels_<ISO_CODE>.properties files.

Here is my labels_en.properties:


action.send.registration.invite.confirm=Are you sure you want to email registration link to the selected Customers?
action.send.registration.invite.sent=Registration email(s) sent.
action.send.registration.invite.sent.title=Registration Invite Status

With this, we will publish our email event once we have a disabled B2B Customer selected and we click the action button.

Adding the Action Button to the Widget

Add the following code to <yourProject>-backoffice-config.xml:

<context merge-by="module" type="B2BCustomer" component="listviewactions" parent="Customer">
    <y:actions xmlns:y="http://www.hybris.com/cockpit/config/hybris">
         <y:group qualifier="common">
              <y:label>actiongroup.common</y:label>
              <y:action action-id="com.<site>.registration.action.sendregistrationinvite" property="selectedObjects" />
              <y:action action-id="com.hybris.cockpitng.action.delete" property="selectedObjects" triggerOnKeys="#del"/>
              <y:action action-id="com.hybris.cockpitng.action.create" property="pageable.typeCode" />
              <y:action action-id="com.hybris.cockpitng.listview.action.export.csv" property="exportColumnsAndData"/>
         </y:group>
    </y:actions>
</context>

Reset the configuration from Backoffice Orchestrator and see the results:

action buttons

We are done with this implementation. Make sure to have an event listener for the email event we published!

To view all of our posts on Backoffice in SAP Hybris, please click here.


19 Comments

rajesh · February 4, 2019 at 1:01 pm

Hi Waqas,

can i write same type of logic that i need to download the prices to csv file based on some conditions

    waqasaslam · February 5, 2019 at 8:59 pm

    Yes, it’s definetly possible. Hybris does this everywhere. Look at the export action button in the Orders and how Hybris exports the orders to a CSV.

Ravi · March 15, 2019 at 12:28 pm

under context action-id “com.batory.registration.action.sendregistrationinvite”, can we define any package??

    waqasaslam · March 22, 2019 at 4:24 pm

    Yes, it can be any package.

Abhishek · June 18, 2019 at 12:01 pm

Hey Aslam,
Very Nice Explaination!
M Beginner in Hybris, n Got stuck at one place i.e wants a page or section to get refresh after any action let’s say delete.(Means m trying to delete a particular list but after delete operation page is not getting refreshed), So can you please help in this.TIA:)

Keith Carlton · March 3, 2020 at 10:19 pm

Nice explanation. Can you please show an example of the finished result? Where did the action button end up? Is it in the actions toolbar or a custom widget?

    Keith Carlton · March 4, 2020 at 5:11 pm

    Nevermind… I missed the image at the bottom

Deepak Pandey · March 27, 2020 at 7:44 am

Getting a WARN [hybrisHTTP20] [Action] could not find action definition for code [com.myextension.backoffice.actions.SendRegistrationInviteAction] always. Can you help here.

    waqasaslam · March 28, 2020 at 10:39 pm

    Did you create the action button definition correctly and put it in the correct folder?

akshay · March 30, 2020 at 7:45 pm

I have follow the same process but icon is not visible.Please Help.

mybackoffice-backoffice-config.xml

definition.xml

Test Preview
Toolbar action displaying a preview popup
1.0

icons/tomato_enabled.png
icons/tomato_hover.png
icons/tomato_disabled.png
com.amway.lynx.mybackoffice.services.bulkupload.TestPreviewAction

    waqasaslam · April 2, 2020 at 10:32 pm

    Do you see any warning/errors when you view the widget in Backoffice?

Ajit · April 1, 2020 at 1:23 pm

Hi Aslam,
I am using hybris 1905, I did all step u mention but am not able to see a custom button on the page.

    waqasaslam · April 2, 2020 at 10:31 pm

    Did you update the file -backoffice-config.xml with your code? What warning/error are you seeing in the log when you access the widget in Backoffice?

      Ajit · April 3, 2020 at 4:24 pm

      Yes I update -backoffice-config.xml.
      but in console am getting below warning

      WARN [hybrisHTTP38] [DefaultActionRenderer] Illegal configuration! Action class ‘com.stroetmannbackoffice.actions.ProductExclusiveGroupsAutomation’ requires ‘java.lang.String’ as input, yet configured to be used in context of ‘java.util.LinkedHashSet’

      ProductExclusiveGroupsAutomation is my action class
      public class ProductExclusiveGroupsAutomation implements CockpitAction{

      //logic

      }

Ajit · April 3, 2020 at 2:17 pm

I did all implementation steps mention above and reset the configuration from Backoffice Orchestrator
but however, I still cannot see the button in the BackOffice page

Mugdha · June 4, 2020 at 4:47 pm

Need help for custom backoffice extension to design an action class. I need to create an action class for a backoffice wizard ‘NEXT’ button. When the NEXT button is clicked after filling in the details, these must be validated from the response of the REST API’s and based on that the SUCCESS AND ERROR POPUP messages need to be displayed and also the data must be saved in the Database

HB · June 26, 2020 at 2:52 pm

Can this action be called inside editor area of an item type?

Pramod J · July 21, 2020 at 5:53 am

Hi,

I have followed the similar steps for one of my requirement but ‘Actions’ are not getting enabled as canPerform method is always returning false because ActionContext Object ctx is getting null always.
I have tried some workarounds but no luck.
Workaround are like changing properties -> property=”selectedObjects” or property=”currentObject”

in definition.xml –

de.hybris……..CustomModel
java.lang.Object

in Action Class –

@Override
public boolean canPerform(ActionContext ctx)
{
return ctx != null && ctx.getData() instanceof CustomModel
}

jyoshna · March 10, 2021 at 3:16 pm

I have a customproductcockpit extension created and web module enabled. i.e different URl to access localhost:9002/customproductcockpit.
Now I want to add this in Backoffice main slot as one of the perspective options. I want to add one more option along with Administration and ProductCockpit.

Leave a Reply

Avatar placeholder

Your email address will not be published. Required fields are marked *