loio |
---|
c9723f8265f644af91c0ed941e114d46 |
view on: demo kit nightly build | demo kit latest release
The sap.ui.model.odata.v4.ODataListBinding#create
method creates a new entity. Users can interact with a newly created entity even before it has been sent to the server.
The
sap.ui.model.odata.v4.ODataListBinding#create
method is also used for creating records in controller code. For more information, see Accessing Data in Controller Code.
To create new entities, ODataListBinding#create
uses the list binding's update group ID as group ID. For more information how this group ID is determined, see the documentation for the binding-specific parameter $$updateGroupID
of ODataModel#bindList
.
A newly created entity can be inserted at the start or at the end of the list. This new entity is transient until it is successfully submitted, see Context#isTransient
. The initial data for the created entity can be supplied via the parameter oInitialData
and modified via property bindings or Context#setProperty
. Properties that are not part of the initial data show the default value from the service metadata on the UI, but they are not sent to the server. If there is no default value, null is used instead, even if the property is not nullable. Updates for the transient entity are collected and added to the POST request which creates the entity on the server. The initial data may contain instance annotations.
Inserting an entity at the end of the list is done via the bAtEnd
parameter in the create
call. The first call determines the overall position of created contexts within the binding's context list. When creating the first new entity at the beginning of the list, every succeeding insertion is relative to the created contexts within this list. Creation at the end of the list is only possible if the list's length has been requested via the $count
system query option. ODataListBinding#isFirstCreateAtEnd
can be used to determine whether any entries have already been created, and whether the first created entry was placed at the end of the list.
To delete transient entities, use Context#delete
. Active transient entities are also deleted when you reset the changes for the list binding on which the entity has been created, see ODataListBinding#resetChanges
and ODataModel#resetChanges
. The promise returned by Context#created
is rejected in all cases where the created entity is deleted before it is created in the back end. As long as the list binding has a transient entity, ODataListBinding#hasPendingChanges
returns true
. Transient entities are handled by the following methods:
setContext
for all bindings,changeParameters
,filter
,refresh
/requestRefresh
,sort
, andsuspend
/resume
for root bindings and bindings using$$ownRequest
; for other bindings, these methods raise an error.
Use the bIgnoreKeptAlive
parameter of ODataListBinding#hasPendingChanges
to check for pending changes which cannot be ignored. The deletion of another entity of the same list binding is possible.
The position of the created entity may change after the methods
refresh
,filter
, orsort
of anODataListBinding
.
If you have called ODataListBinding#create
on a list binding where the update group ID has SubmitMode.API
and the creation of the entity on the server fails, the creation is repeated with the next call of submitBatch
for this group. If the update group ID has SubmitMode.Auto
or SubmitMode.Direct
and the creation fails, the creation is repeated automatically with the next update for the entity. submitBatch
can also be used for update group IDs with SubmitMode.Auto
to repeat, independently of an update. The error returned by the server is passed to the MessageManager
and the promise you get via Context.created
is not rejected. Each time the data for the created entity is sent to the server, a v4.ODataListBinding.createSent
event is fired. Each time the client receives a response for the creation, a v4.ODataListBinding.createCompleted
event is fired, independent of whether the creation was successful or not.
Lock the UI each time the (
POST
) request for the creation is sent to the server and unlock it, when the response from the server for that (POST
) request is processed, because updates in between result in errors. If the update group ID isSubmitMode.API
, you can lock the UI when callingODataModel#submitBatch
and unlock it again when the promise returned byODataModel#submitBatch
is resolved or rejected. However, if the update group ID isSubmitMode.Auto
orSubmitMode.Direct
, use thecreateSent
event to lock the related UI and thecreateCompleted
event to unlock it.
// suppose this list binding has no own update group; it uses the model's update group instead (an application group)
...
onCreateSalesOrder : function (oEvent) {
var oContext = this.getView().byId("SalesOrders").getBinding("items")
.create({
"Note" : "My new Sales Order",
"NoteLanguage" : "E",
"BuyerID" : "0100000000",
"CurrencyCode" : "EUR"
});
// Note: This promise fails only if the transient entity is canceled,
// i.e. deleted by either deleting the transient context or by resetting pending changes
oContext.created().then(function () {
// sales order successfully created
}, function (oError) {
// handle rejection of entity creation; if oError.canceled === true then the transient entity has been deleted
if (!oError.canceled) {
throw oError; // unexpected error
}
});
},
onDeleteSalesOrder : function () {
var oSalesOrderContext = this.getView().byId("SalesOrders").getSelectedItem().getBindingContext();
oSalesOrderContext.delete("$auto").then(function () {
// sales order successfully deleted
}, function (oError) {
// do error handling
});
},
onSaveSalesOrder : function () {
var oView = this.getView();
function resetBusy() {
oView.setBusy(false);
}
// lock UI until submitBatch is resolved, to prevent errors caused by updates while submitBatch is pending
oView.setBusy(true);
oView.getModel().submitBatch(oView.getModel().getUpdateGroupId()).then(resetBusy, resetBusy);
},
...
To ensure that for a list binding all expanded data is available as soon as the promise returned by
Context#created
is resolved, an additional singleGET
request for the newly created entity is sent automatically once thePOST
request has arrived.If you want to skip this additional single
GET
request, callODataListBinding#create
with parameterbSkipRefresh=true
.
The promise
returned by Context#created
is resolved when the entity represented by this context has been created in the backend. Once the promise is resolved, sap.ui.model.Context#getPath
returns a path including the key predicate of the new entity. For returning the path including the key predicates, all key properties need to be available.
It is also possible to create nested entities in a navigation property with a single request together with their parent entity (so-called "deep create"; the request itself is called "deep insert" in the OData specification).
Bind the list for the nested collection relative to the transient context of the created main entity. A create
in the nested table then creates a row which contributes to the POST request of the main entity. A deep create is not restricted to one level; it is also possible that a nested entity has a nested collection itself.
"Deep create" supports a simple collection-valued navigation property (no path like "detail/items"). It requires that the autoExpandSelect
model parameter is true
. There must be no context binding in the binding hierarchy between the top-level and nested list binding.
Example: Creating a sales order with line items
View
<SimpleForm id="form"> <Table id="items" items="{path : 'SO_2_SOITEM', parameters : {$$ownRequest : true}}"> <ColumnListItem> <Input value="{ProductID}"/> <Input value="{Amount}"/> <Input value="{Unit}"/> </ColumnListItem> </Table> </SimpleForm>
Controller
createNewSalesOrder : function () { var oSalesOrderBinding = this.getView().getModel().bindList("/SalesOrderList"); this.oNewSalesOrderContext = oSalesOrderBinding.create(); this.oNewSalesOrderContext.created().then(function () { // successfully created }, function () { // creation canceled (if the request failed, it will be // automatically queued again) }; this.byId("form").setBindingContext(this.oNewSalesOrderContext); }, addItem : function () { oNewItemContext = this.byId("items").getBinding("items").create(); }, deleteItem : function () { this.byId("items").getSelectedItem().getBindingContext().delete(); }, save : function () { var oModel = this.getView().getModel(); oModel.submitBatch(oModel.getUpdateGroupId()); }, cancel : function () { this.oNewSalesOrderContext.delete(); // alternatively // oModel.resetChanges(oModel.getUpdateGroupId()); }
The createNewSalesOrder
function creates a sales order and binds it to the form. Initially this is no deep create. The addItem
and deleteItem
functions show that you can create and delete nested items exactly like top-level items. They automatically add and remove an item in the nested collection of the top-level POST request. As soon as the first item has been added to the "items" table, the create
becomes a deep create. create
returns a context allowing to access the transient item's data. The isTransient
value of such a nested context is true
, and it has a created
promise. But there are two important differences:
- A nested context becomes invalid after a successful deep create. New contexts are created for the nested collection, because it is not possible to reliably assign the response entities to those of the request, especially if the count differs.
- Never use the
created
promise of a nested context! This context is about to be destroyed anyway, and it is not possible to access the created data through it. So the promise is always rejected, even if the creation succeeded.
It is also possible to create a sales order already having items in the initial data. These items then show up in the table immediately. They have transient contexts in the "items" table's binding, which can be accessed via getAllCurrentContexts
:
this.oNewSalesOrderContext = oSalesOrderBinding.create({ SO_2_SOITEM : [ {ProductID : "1", Amount : "1", Unit : "EA"}, {ProductID : "2", Amount : "3.7", Unit : "kg"} ] }); aInitialItemsContexts = this.byId("items").getBinding("items").getAllCurrentContexts();
The usage of the bSkipRefresh
parameter when calling create
is not recommended for a deep create. It leads to multiple single row requests if the POST response did not supply all properties of the nested list. If it is not set, the model checks whether all required properties and child entities are available on the client and requests only data that is missing.
Canceling a deep create or deleting the top-level entity removes the request, the top-level entity, and all its subentities. Deleting a nested entity also deletes its subentities.
As long as the parent context is still transient, requesting data from the server does not make sense. Hence, API functions leading to a server request (like refresh
, sort
, filter
, requestSideEffects
) are forbidden. When the parent entity including its nested entities has been created, the binding is fully functional.
No API is required here. Simply bind the properties of the nested entity relative to the transient context of the created main entity. If the user enters a value in such a field, this value is automatically added to the payload of the POST request, making it a deep create.
View
<SimpleForm> <Text text="{SalesOrderID}"/> <Text text="{SO_2_BP/BusinessPartnerID}"/> <Input value="{SO_2_BP/CompanyName}"/> </SimpleForm>
In this (unrealistic) example, it is possible to create the business partner together with the sales order. As soon as the user enters a company name, the POST request for the sales order contains the SO_2_BP
navigation property and thus becomes a deep create.
It is also possible to have this navigation property in the initial data.
In some applications users need to be able to quickly enter a large amount of new records. A "Create" button or the related keyboard shortcut that needs to be explicitly pressed would slow down the user. To avoid this, the application could provide multiple inline creation rows in the table that are initially filled with default values and are not persisted in the back end.
You can create such an inline creation row by calling sap.ui.model.odata.v4.ODataListBinding#create
with the bInactive
parameter. Call it multiple times for multiple rows. These rows are called inactive because they are not sent to the server. Once the user modifies a property of such an inactive row, and the modification passes the client-side validation, a v4.ODataListBinding.createActivate
event is fired and the row will be sent to the server with the next batch for the binding's update group. You could use the event for additional custom validation, and it supports calling sap.ui.base.Event#preventDefault
to prevent the row from being sent to the server. You could use this, for example, to require certain properties to be set before sending the row to the server. You could also reset the row back to the initial state via Context#resetChanges
. The event can also be used to create a new inline creation row. While inactive, an inline creation row does not count as a pending change and does not contribute to the collection's count.
persisted
: An already existing entity that was read from the server.inactive
: An inline creation row without any property update yet; it is waiting for a property change before adding a POST to the batch queue.transient
: A POST is waiting in the batch queue.createPending
: The POST has been sent to the server; the entity is waiting for the response.parked
: A POST via an auto group that failed is parked until a property update takes place.createdPersisted
: The POST succeeded, and the entity now exists on the server. If the binding is refreshed, the context's reaction depends on itsisKeepAlive
state. If it's set tofalse
, the context is dropped and created anew when it's read from the server. If it's set totrue
, it is refreshed with a special request and remains in thecreatedPersisted
state.
Internal States of an OData V4 Binding Context
The state of a context can be checked via the following API functions:
-
Context#isTransient
. This function does not distinguish between thetransient
,createPending
, andparked
states of a context. It returnstrue
for any of them; the internal state of a such a context should not matter to the application.Context#isTransient
also returnstrue
forinactive
contexts. It returnsfalse
forcreatedPersisted
contexts, andundefined
forpersisted
contexts. The value ofisTransient()
can also be observed via the bindable annotation@$ui5.context.isTransient
. -
Context#isInactive
: This function returnstrue
forinactive
contexts,false
for contexts that were created in theinactive
state and have been activated, andundefined
otherwise. The value ofisInactive()
can also be observed via the bindable annotation@$ui5.context.isInactive
.
Related Information
sap.ui.model.odata.v4.ODataListBinding
sap.ui.model.odata.v4.ODataModel