Custom Actions and Functions
Defining Custom Actions
In addition to common CRUD operations, you can declare domain-specific custom operations as shown below. These custom operations always need custom implementations in corresponding events handlers.
You can define actions and functions in CDS models like that:
service Sue {
// unbound actions & functions
function sum (x:Integer, y:Integer) returns Integer;
function stock (id : Foo:ID) returns Integer;
action add (x:Integer, to: Integer) returns Integer;
// bound actions & functions
entity Foo { key ID:Integer } actions {
function getStock() returns Integer;
action order (x:Integer) returns Integer;
// bound to the collection and not a specific instance of Foo
action customCreate (in: many $self, x: String) returns Foo;
// All parameters are optional by default, unless marked with `not null`:
action discard (reason: String not null);
}
}Learn more about modeling actions and functions in CDS.
Kinds of Actions
The differentiation between Actions and Functions as well as bound and unbound stems from the OData specifications, and in essence is as follows:
- Actions modify data in the server
- Functions retrieve data
- Unbound actions/functions are like plain unbound functions in JavaScript.
- Bound actions/functions always receive the bound entity's primary key as implicit first argument, similar to
thispointers in Java or JavaScript. The exception are bound actions to collections, which are bound against the collection and not a specific instance of the entity. An example use case are custom create actions for the SAP Fiori elements UI.
Implementing Actions
In general, implement actions or functions like that:
module.exports = function Sue(){
this.on('sum', ({data:{x,y}}) => x+y)
this.on('add', ({data:{x,to}}) => stocks[to] += x)
this.on('stock', ({data:{id}}) => stocks[id])
this.on('getStock','Foo', ({params:[id]}) => stocks[id])
this.on('order','Foo', ({params:[id],data:{x}}) => stocks[id] -= x)
}Event handlers for actions or functions are very similar to those for CRUD events, with the name of the action/function replacing the name of the CRUD operations. No entity is specific for unbound actions/functions.
Method-style Implementations in Node.js, you can alternatively implement actions and functions using conventional JavaScript methods with subclasses of cds.Service:
module.exports = class Sue extends cds.Service {
sum(x,y) { return x+y }
add(x,to) { return stocks[to] += x }
stock(id) { return stocks[id] }
getStock(Foo,id) { return stocks[id] }
order(Foo,id,x) { return stocks[id] -= x }
}Calling Actions / Functions
HTTP Requests to call the actions/function declared above look like that:
GET .../sue/sum(x=1,y=2) // unbound function
GET .../sue/stock(id=2) // unbound function
POST .../sue/add {"x":11,"to":2} // unbound action
GET .../sue/Foo(2)/Sue.getStock() // bound function
POST .../sue/Foo(2)/Sue.order {"x":3} // bound actionNote: You always need to add the
()for functions, even if no arguments are required. The OData standard specifies that bound actions/functions need to be prefixed with the service's name. In the previous example, entityFoohas a bound actionorder. That action must be called via/Foo(2)/Sue.orderinstead of simply/Foo(2)/order. For convenience, you may:
- Call bound actions/functions without prefixing them with the service name.
- Omit the
()if no parameter is required.- Use query options to provide function parameters like
sue/sum?x=1&y=2
Programmatic usage via generic APIs for Node.js:
For unbound actions and functions:
async function srv.send (
event : string | { event, data?, headers?: object },
data? : object | any
)
return : result of this.dispatch(req)For bound actions and functions:
async function srv.send (
event : string | { event, entity, data?, params?: array of object, headers?: object },
entity : string,
data? : object | any
)
return : result of this.dispatch(req)eventis a name of a custom action or functionentityis a name of an entityparamsare keys of the entity instance
Programmatic usage would look like this for Node.js:
const srv = await cds.connect.to('Sue')
// unbound actions/functions
await srv.send('sum',{x:1,y:2})
await srv.send('stock',{id:2})
await srv.send('add',{x:11,to:2})
// actions/functions bound to collection
await srv.send('getStock','Foo',{id:2})
// for actions/functions bound to entity instance, use this syntax
await srv.send({ event: 'order', entity: 'Foo', data: {x:3}, params: [{id:2}]})Note: Always pass the target entity name as second argument for bound actions/functions.
Programmatic usage via typed API — Node.js automatically equips generated service instances with specific methods matching the definitions of actions/functions found in the services' model. This allows convenient usage like that:
const srv = await cds.connect.to(Sue)
// unbound actions/functions
srv.sum(1,2)
srv.stock(2)
srv.add(11,2)
// bound actions/functions
srv.getStock('Foo',2)
srv.order('Foo',2,3)Note: Even with that typed APIs, always pass the target entity name as second argument for bound actions/functions.