In this step, we will make it possible to create and edit (update) user data from the user interface and send the data to the back end.
Data can now be edited and added.
You can view and download all files at OData V4 - Step 6.
...
onInit : function () {
var oMessageManager = sap.ui.getCore().getMessageManager(),
oMessageModel = oMessageManager.getMessageModel(),
oMessageModelBinding = oMessageModel.bindList("/", undefined, [],
new Filter("technical", FilterOperator.EQ, true)),
oViewModel = new JSONModel({
busy : false,
hasUIChanges : false,
usernameEmpty : true,
order : 0
});
this.getView().setModel(oViewModel, "appView");
this.getView().setModel(oMessageModel, "message");
oMessageModelBinding.attachChange(this.onMessageBindingChange, this);
this._bTechnicalErrors = false;
},
...
We change the onInit
method: The appView
model receives two additional properties, which we will use to control whether certain controls in the view are enabled or visible during user entries. We also make the MessageModel
available to the view and add a ListBinding
. When the OData service reports errors while writing data, the OData Model adds them to the MessageModel
as technical messages. Therefore we apply a filter to the ListBinding
. We register our own handler to the change
event of that ListBinding
in order to capture any errors.
...
onSort : function () {
...
},
_getText : function (sTextId, aArgs) {
...
},
_setUIChanges : function (bHasUIChanges) {
if (this._bTechnicalErrors) {
// If there is currently a technical error, then force 'true'.
bHasUIChanges = true;
} else if (bHasUIChanges === undefined) {
bHasUIChanges = this.getView().getModel().hasPendingChanges();
}
var oModel = this.getView().getModel("appView");
oModel.setProperty("/hasUIChanges", bHasUIChanges);
}
});
});
We add the _setUIChanges
private method that lets us set the property hasUIChanges
of the appView
model. Unless there are currently technical messages in the MessageModel
or it is called with a given value for its bHasUIChanges
parameter, the method uses ODataModel.hasPendingChanges
. That method returns true
if there are any changes that have not yet been written to the service.
...
onInit: function () {
...
},
onCreate : function () {
var oList = this.byId("peopleList"),
oBinding = oList.getBinding("items"),
oContext = oBinding.create({
"UserName" : "",
"FirstName" : "",
"LastName" : "",
"Age" : "18"
});
this._setUIChanges();
this.getView().getModel("appView").setProperty("/usernameEmpty", true);
oList.getItems().some(function (oItem) {
if (oItem.getBindingContext() === oContext) {
oItem.focus();
oItem.setSelected(true);
return true;
}
});
},
onRefresh
...
We add the onCreate
event handler that responds to the press
event of the Add User button. We use the create
method of the ODataListBinding
API to create a new user with some initial data and insert it at the top of the table. The create
method returns the binding context of the new user. That context provides a created
method which returns a Promise
. The Promise
is resolved when the new user is successfully transferred to the OData service.
We also use the binding context returned by the create
method to focus and select the new row in which the new data can be entered.
...
onRefresh: function () {
...
},
onSave : function () {
var fnSuccess = function () {
this._setBusy(false);
MessageToast.show(this._getText("changesSentMessage"));
this._setUIChanges(false);
}.bind(this);
var fnError = function (oError) {
this._setBusy(false);
this._setUIChanges(false);
MessageBox.error(oError.message);
}.bind(this);
this._setBusy(true); // Lock UI until submitBatch is resolved.
this.getView().getModel().submitBatch("peopleGroup").then(fnSuccess, fnError);
this._bTechnicalErrors = false; // If there were technical errors, a new save resets them.
},
onSearch: function () {
...
},
...
_setUIChanges : function (bHasUIChanges) {
...
},
_setBusy : function (bIsBusy) {
var oModel = this.getView().getModel("appView");
oModel.setProperty("/busy", bIsBusy);
}
});
});
We create the onSave
event handler, in which we call the submitBatch
method of the ODataModel
API to submit our changes. Because the changes we submit refer to the table, we need to pass the update group peopleGroup
that we declared in the table binding.
The submitBatch
method returns a Promise
that is rejected only if the batch request itself fails, for example, if the OData service is unavailable or if there were authorization problems. It is resolved in all other cases, also if the service returns errors for single requests that are contained in the batch request. Therefore, we have to implement the error handling for single requests differently.
We also define a _setBusy
private function to lock the whole UI while the data is submitted to the back end.
...
onSort : function () {
...
},
onMessageBindingChange : function (oEvent) {
var aContexts = oEvent.getSource().getContexts(),
aMessages,
bMessageOpen = false;
if (bMessageOpen || !aContexts.length) {
return;
}
// Extract and remove the technical messages
aMessages = aContexts.map(function (oContext) {
return oContext.getObject();
});
sap.ui.getCore().getMessageManager().removeMessages(aMessages);
this._setUIChanges(true);
this._bTechnicalErrors = true;
MessageBox.error(aMessages[0].message, {
id : "serviceErrorMessageBox",
onClose : function () {
bMessageOpen = false;
}
});
bMessageOpen = true;
},
...
We implement the event handler for the change
event of the ListBinding
to the MessageModel
. We created the ListBinding
with a filter to only include technical messages. That means that the change
event will be fired with every change but only technical messages will have a binding context. In case of technical messages, we get the first one and display it as an error. We also make sure that the toolbar for saving or discarding changes stays visible. We delete the technical messages so that they do not accumulate.
...
onRefresh: function () {
...
},
onResetChanges : function () {
this.byId("peopleList").getBinding("items").resetChanges();
this._bTechnicalErrors = false;
this._setUIChanges();
},
onSearch: function () {
...
},
...
The onResetChanges
method handles discarding pending changes. It uses the resetChanges
method of the ODataListBinding
API to remove any such changes. Then it calls the _setUIChanges
private method to enable the elements of the header toolbar again and hide the footer.
...
onCreate: function () {
...
},
onInputChange : function (oEvt) {
if (oEvt.getParameter("escPressed")) {
this._setUIChanges();
} else {
this._setUIChanges(true);
if (oEvt.getSource().getParent().getBindingContext().getProperty("UserName")) {
this.getView().getModel("appView").setProperty("/usernameEmpty", false);
}
}
},
onRefresh : function () {
...
},
...
The onInputChange
event handler manages entries in any of the Input
fields and triggers updates to the appView
model as needed. It does an extra check on the UserName
field to make sure that users cannot be saved without a UserName
. Otherwise the OData service would return errors because UserName
is a mandatory field.
<mvc:View
controllerName="sap.ui.core.tutorial.odatav4.controller.App"
displayBlock="true"
xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc">
<Shell>
<App busy="{appView>/busy}" class="sapUiSizeCompact">
<pages>
<Page title="{i18n>peoplePageTitle}">
<content>
<Table
id="peopleList"
growing="true"
growingThreshold="10"
items="{
path: '/People',
parameters: {
$count: true,
$$updateGroupId : 'peopleGroup'
}
}">
<headerToolbar>
<OverflowToolbar>
<content>
<ToolbarSpacer/>
<SearchField
id="searchField"
width="20%"
placeholder="{i18n>searchFieldPlaceholder}"
enabled="{= !${appView>/hasUIChanges}}"
search=".onSearch"/>
<Button
id="addUserButton"
icon="sap-icon://add"
tooltip="{i18n>createButtonText}"
press=".onCreate">
<layoutData>
<OverflowToolbarLayoutData priority="NeverOverflow"/>
</layoutData>
</Button>
<Button
id="refreshUsersButton"
icon="sap-icon://refresh"
enabled="{= !${appView>/hasUIChanges}}"
tooltip="{i18n>refreshButtonText}"
press=".onRefresh"/>
<Button
id="sortUsersButton"
icon="sap-icon://sort"
enabled="{= !${appView>/hasUIChanges}}"
tooltip="{i18n>sortButtonText}"
press=".onSort"/>
</content>
</OverflowToolbar>
</headerToolbar>
<columns>
<Column id="userNameColumn">
<Text text="{i18n>userNameLabelText}"/>
</Column>
<Column id="firstNameColumn">
<Text text="{i18n>firstNameLabelText}"/>
</Column>
<Column id="lastNameColumn">
<Text text="{i18n>lastNameLabelText}"/>
</Column>
<Column id="ageColumn">
<Text text="{i18n>ageLabelText}"/>
</Column>
</columns>
<items>
<ColumnListItem>
<cells>
<Input
value="{UserName}"
valueLiveUpdate="true"
liveChange=".onInputChange"/>
</cells>
<cells>
<Input
value="{FirstName}"
liveChange=".onInputChange"/>
</cells>
<cells>
<Input
value="{LastName}"
liveChange=".onInputChange"/>
</cells>
<cells>
<Input
value="{Age}"
valueLiveUpdate="true"
liveChange=".onInputChange"/>
</cells>
</ColumnListItem>
</items>
</Table>
</content>
<footer>
<Toolbar visible="{appView>/hasUIChanges}">
<ToolbarSpacer/>
<Button
id="saveButton"
type="Emphasized"
text="{i18n>saveButtonText}"
enabled="{= ${message>/}.length === 0 && ${appView>/usernameEmpty} === false }"
press=".onSave"/>
<Button
id="doneButton"
text="{i18n>cancelButtonText}"
press=".onResetChanges"/>
</Toolbar>
</footer>
</Page>
</pages>
</App>
</Shell>
</mvc:View>
We add the $$updateGroupId: 'peopleGroup'
parameter to the table. This means that changes in the table are not sent to the service immediately but instead are collected until we explicitly send them.
We add a new Add User button to the overflow toolbar in the table header, and define a footer toolbar that contains Save and Cancel buttons that we can display or hide through the appView
model. We can disable the Save button separately, for example when a user enters invalid data.
Finally, we add the liveChange="onInputChange"
event handler to the table cells to make it possible to react to user input. In addition, we set the valueLiveUpdate
properties for the fields for UserName
and Age
. That makes sure that the SAPUI5 types validate the field content with each keystroke.
Creation via a form is demonstrated in our Sales Orders sample app.
# Toolbar
#XBUT: Button text for save
saveButtonText=Save
#XBUT: Button text for cancel
cancelButtonText=Cancel
#XBUT: Button text for add user
createButtonText=Add User
#XTOL: Tooltip for sort
sortButtonText=Sort by Last Name
...
# Messages
#XMSG: Message for user changes sent to the service
changesSentMessage=User data sent to the server
...
We add the new message texts.
Related Information
Model Instantiation and Data Access