Skip to content

Latest commit

 

History

History
419 lines (339 loc) · 14.1 KB

step-6-create-and-edit-b4f1266.md

File metadata and controls

419 lines (339 loc) · 14.1 KB

Step 6: Create and Edit

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 &amp;&amp; ${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

Batch Control

OData Operations

Creating an Entity in a Collection

Message Model

API Reference: sap.ui.model.odata.v4.ODataContextBinding