Retail:How to create new actions
Contents |
Introduction
The new functionality Customizing UI through Touchpoint UI Configuration allows to configure the action buttons that appear in the defined button areas of the POS terminal main window. With this configuration all main actions of the POS terminal can be quickly reachable depending on the terminal type or business requirements.
New actions or existing actions of the POS application can be refactored in order to allow the POS application user to configure where the button that starts the action goes. This way, actions can be executed not only where the developer of the action designs. It can be placed in any of the button areas provided by the application or even hidden if needed.
This new functionality to define actions its not only intended to allow the POS application user to configure where the button that starts the action goes. It also allows the developer to start the action in the required place and share all the code, and definition of the action. To refactor or define new actions you need to be familiar with the Openbravo Modularity_Concepts and with the Openbravo Category:Retail_Developers_Guide.
Dictionary declaration
The first step is to add to the dictionary the declaration of the new action. This is done as Openbravo System Administration in the window Touchpoint Actions. In this window are defined all the available actions. This definition is needed for the UI Configuration windows to allow to select the new action you create and place it in any of the button areas of the application.
To declare a new action fill in the following fields of the header tab:
- Module: Defines the module your action is implemented. The module must be in development mode in order to be able to add new action declarations. Note that the implementation of the action must go in this module too.
- Window: This is the name of the mobile window this action can be invoked and if the window contains button areas, the button areas a button that starts this action can be added. If the action can be invoked from any mobile window, select ALL Mobile. The Search key of the selected window is used in the code that implements the action and it is the URL hash of the mobile window. See below the list of available windows.
- Search Key: The unique identifier for this action. This is the identifier to use in the code that implements the action.
- Name: A non unique identifier for documentation purposes.
- Description: An optional long description of what the action does for documentation purposes.
Then in the child tab you define the Action Component that can be used in the configuration window to start this action and what is the default Action Component. The Action Component is basically the UI element that is used to start the action. The available options are:
- Action Button: A regular button. The action is started when the user clicks on the button.
- Action Button Key: It is like the Action Button but with the property avoidDoubleClick deactivated to allow quick repetitions of the action. It is intended for simple keyboard actions like typing numbers, letters, ...
- Action Button Switch: An actionable button. This Action Component is commonly used in action that requires an user input, for example to change the quantity or the price of a line. To start the action the user first clicks the button to select the action. Then types the input. And clicks on the button again or the enter button to start the action.
Once the action is declared you can export the data to generate the XML source files for your module. To export the data use the same build command used for other dictionary declarations like, tables, windows, messages etc.. Open a terminal window and execute from the root folder of the Openbravo sources the following command:
ant export.database
To verify the declaration of the action has been properly exported to the dictionary, check the following files in your module folder have been created and contains the data of your action.
src-db/database/sourcedata/OBMOBC_ACTION.xml src-db/database/sourcedata/OBMOBC_ACTION_CTYPE.xml
Javascript implementation
Action definition, window
and name
After the action is declared in the dictionary you can start with the implementation of the action. As explained before, the implementation must be done in the same module the action is declared.
This is the most simple action that can be implemented. This action just displays an alert when clicking on the button that fires it.
OB.MobileApp.actionsRegistry.register( new OB.Actions.CommandAction({ window: 'retail.pointofsale', name: 'myNewAction', command: function(view) { alert('My new action is executed'); } }) );
The field window
is the Search Key of the window Main Web POS and the field name
is the Search Key of the Mobile Action. And command()
is the function executed.
More fields, permission
and properties
Now we have defined a basic action we can add more functionality to the action. Like the permission the logged user must have to be able to execute the action. And in case the user does not have this permission. The Action Button that starts it will be disabled.
OB.MobileApp.actionsRegistry.register( new OB.Actions.CommandAction({ window: 'retail.pointofsale', name: 'myNewAction', permission: 'OBPOS_print.receipt', ... }) );
In the previous example the Action Button that executes myNewAction
will be disabled if the user logged in does not have permissions for the preference </code>OBPOS_print.receipt</code> that corresponds to the permission Web POS action Print receipt.
To define the the localized label of the Action Button that fires the action use the field properties. For example:
OB.MobileApp.actionsRegistry.register( new OB.Actions.CommandAction({ window: 'retail.pointofsale', name: 'myNewAction', properties: { i18nContent: 'OBPOS_LblPrintReceipt' } ... }) );
This defines the localized label OPBPOS_LblPrintReceipt
that in English is defined as Print this Receipt.
Implementing the command()
function
As you can observe the command function has a parameter named view
. This parameter is the instance of the mobile window currently in execution. It is always an enyo kind that inherits OB.UI.WindowView
, that in the case of the Main Web POS window is an instance of the enyo kind OB.OBPOSPointOfSale.UI.PointOfSale
implemented in the module org.openbravo.retail.posterminal.
Starting from the view
parameter you can access the model of the view, functions, etc... to implement your action. You can find different implementations of actions for the Main Web POS window you can use as a model for your action in the following folder of the module org.openbravo.retail.posterminal https://code.openbravo.com/erp/pmods/org.openbravo.retail.posterminal/file/tip/web/org.openbravo.retail.posterminal/js/actions
One important field of the view
parameter is state
. This field has been created to manage the state of the window and other parameters that has been decided to set at this level for better access when executing actions. From an action command()
the following methods of the state
field can be invoked:
-
view.state.readstate({state})
-
view.state.readcommandstate({state})
Except very special cases use always view.state.readcommandstate({state})
because invoking this function will evaluate also the handlers associated to the state.
This is an example of reading the state that defines whether the current receipt is editable or not:
const isEditable = view.state.readCommandState({ name: 'receipt.isEditable' });
Implementing the isActive()
function
Depending on the state of the application it can be defined whether an action can be invoked or not. For example you cannot change the price of a line in a receipt that is already paid, so you want that the Action Button that changes the price to be disabled. To do this all you have to do is to implement the isActive()
in your action and return true or false depending on the states of the application.
Because whether the action is active or not depends on the state of the application the isActive()
is invoked every time any state changes. This function is also synchronous so in the case you need to do any asynchronous call for example to do a remote request you must declare a new application state.
Invoke the asynchronous request outside the isActive
code and change the new state value when you get the response.
This is a simple example of the isActive()
action function that declares the action as active if and only if the receipt is editable:
isActive: function(view) { return view.state.readCommandState({ name: 'receipt.isEditable' }); }
Defined states
Module org.openbravo.retail.posterminal
There are already a list of states defined by default in the module org.openbravo.retail.posterminal for the Main Web POS window that are linked to the current receipt attributes:
-
receipt.isEditable
-
receipt.generateInvoice
-
receipt.bp
-
receipt.isQuotation
-
receipt.isLayaway
-
receipt.isPaid
-
receipt.hasBeenPaid
-
receipt.orderType
-
receipt.hasServices
-
receipt.gross
-
receipt.replacedOrder
One other linked with the current view attributes:
-
window.currentView
And other with the current user selection:
-
selectedReceiptLine
-
selectedReceiptLines
Module org.openbravo.mobile.core
There is one state defined in the module org.openbravo.mobile.core
-
editbox
This state is linked to the user input. This state is used for example in actions to change the price, change the quantity or to scan a barcode.
States
States are very important in actions because are used as the main parameters for actions as they are very easy to access from view.state
in the functions command()
and isactive()
and because every time any of the defined states is modified, the status active / inactive of all actions is reevaluated.
Declare an state as an attribute of a Backbone object
This is the easiest way to define a new state as the state it is automatically changed when the Backbone attribute is changed. This is an example to link the isEditable
attribute of the receipt Backbone model.
OB.MobileApp.statesRegistry.register( new OB.State.BackboneProperty({ window: 'retail.pointofsale', name: 'receipt.isEditable', object: function(view) { return view.model.get('order'); }, property: 'isEditable' }) );
Managing states from an enyo object
You can always read the state, write a new state value and subscribe to the changes of an state from an enyo object using the enyo events defined in the mobile view onReadState
, onWriteState
and onSubscribeAction
. This enyo object must hang from a mobile view to properly handle the event for example to read an state value:
enyo.kind({ name: 'OB.UI.MyEnyoKind', events: { onReadState: '' }, myMethodThatReadsState: function () { const stateevent = { name: 'myStateName' }; this.doReadState(stateevent); if (stateevent.value...) { ... } } });
You can also write the value of an state this way:
enyo.kind({ name: 'OB.UI.MyEnyoKind', events: { onWriteState: '' }, myMethodThatChangesState: function () { this.doWriteState({ name: 'myStateName', value: 'myStateValue' }); } });
And to subscribe to the changes of an state value. Note that you can subscribe to one or more states just adding more states names to the names array.
enyo.kind({ name: 'OB.UI.MyEnyoKind', events: { onSubscribeAction: '' }, initComponents: function() { this.doSubscribeAction({ names: ['myStateName'] }); }, actionNotify: function(view) { const value = view.state.readState({ name: 'myStateName' }); } });
Calling an action
Actions can be used not only to be executed from buttons configured, but also can be executed directly from javascript. The case can be of one action that has to go in the menu but you want also offer the user that configures the application to call the action from one of the action button areas defined in the application. The Action Components can be used as plain enyo objects from any place you want. For example to add a new menu entry that invokes an action use the enyo kind OB.UI.ActionMenuAction
. OB.OBPOSPointOfSale.UI.LeftToolbarImpl.prototype.menuEntries.push({ kind: 'OB.UI.ActionMenuAction', action: { window: 'retail.pointofsale', name: 'verifiedReturn' } });
Or if you want to execute the action from any other place of the application using a regular button use the enyo kind OB.UI.ActionButton
. In the following example you can see how to create an Action Button component that calls the action verifiedReturn that is created inside another enyo component:
enyo.kind({ name: 'OB.UI.MyActionsContainer', components: [ { kind: 'OB.UI.ActionButton', name: 'verifiedReturn', classes: 'btnlink btnlink-small', action: { window: 'retail.pointofsale', name: 'verifiedReturn' } } ] });
You can also invoke actions without using any of the Action Components already defined. Using the actions registry you can access all actions registered, check whether is active or inactive and execute it. Remember that you can only execute actions if the application is located in the same window of the registered action, otherwise an exception will be raised. As an example, the following code checks first if the verifiedReturn action is active and then executes it.
const actioncommand = OB.MobileApp.actionsRegistry.getActionCommand({ window: 'retail.pointofsale', name: 'verifiedReturn' }); if (actioncommand.canExecute()) { const result = actioncommand.execute(); } else { ... }
Or you can also directly execute the action if you are already sure is active:
OB.MobileApp.actionsRegistry.execute({ window: 'retail.pointofsale', name: 'verifiedReturn' });