2

I have an "Add New…" screen with multiple sap.m.Input fields. Everything is working. I submit the form and the values are stored in the DB. But once I re-open this "Add New…" screen, I get the form with previously entered values.

Currently, I can solve the issue iterating over all sap.m.Input fields with sap.ui.core.Element, resetting the values:

Element.registry.forEach(el => {
    if (el.isA("sap.m.Input") && el.sId.includes(inputFieldsMask)) {
        sap.ui.getCore().byId(el.sId).setValue("");
    }
});

Where inputFieldsMask is a mask for all input fields of the relevant screen.

As far as I understand, Element.registry.forEach iterates over all controls in the app, therefore I'm not sure that, from a performance point of view, it's an optimal approach to clean up the fields.

Is there a better way to reset input fields from the previously entered values?

Mike
  • 14,010
  • 29
  • 101
  • 161

3 Answers3

1

Best practice is to use a model to store your application data and to bind any input field to that model. I added an example here. For the sake of simplicity the model data is cleared when the button is pressed.

In a real world application you would place any setup of the model to the onRouteMatched handler to ensure that the data is in an initial state.

onRouteMatched : function(event) {
    this.getView().getModel().setData({
        "firstName": "",
        "lastName": ""
    });
}
matbtt
  • 4,230
  • 19
  • 26
1

There are several ways to reset the control values depending on what kind of approach you took to create the new entry. Given, for example, relative bindings in the target container definition (e.g. a fragment containing sap.m.Dialog):

  1. Pass a Context instance via targetContainer.setBindingContext or, if possible, let the framework create and set one via targetContainer.bindElement/.bindObject. This resolves all the relative bindings that the context can resolve (if the respective paths are valid), and allows directly manipulating the bound data from the UI via two-way binding.
  2. <user enters some data and submits ...>
  3. Call targetContainer.unbindElement/.unbindObject after the edit was successfully submitted.

By unbinding element, the framework cleans up the internal element binding information of the target container and resets all its relatively bound control values of the given model.

Keep in mind that the API unbindElement/unbindObject awaits a model name that may be required.

Example (client-side model):

globalThis.onUI5Init = () => sap.ui.require([
  "sap/ui/core/mvc/Controller",
  "sap/ui/core/mvc/XMLView",
  "sap/ui/model/json/JSONModel", // Sample model for the sake of this demo
  "sap/ui/core/Core",
], async (Controller, XMLView, JSONModel, Core) => {
  "use strict";
  
  const MyController = Controller.extend("demo.MyController", {
    handleAddPress: function() {
      const dialog = this.byId("myDialog");
      const clientListBinding = this.byId("myList").getBinding("items");
      // In case of OData and if no context exists yet, consider creating a new context via odataListBinding.create, v4.ODataModel#getKeepAliveContext, etc.
      clientListBinding.suspend(); // to update the list once the dialog closes
      this._currentItems = this.getView().getModel().getProperty("/myItems"); // for the cancel case (onESCPress)
      dialog.getModel().setProperty("/myItems", this._currentItems.concat({})); // new empty item. Applies only to this client-side model
      dialog.bindElement(`/myItems/${/*index:*/clientListBinding.getLength()}`); // or setBindingContext
      dialog.open();
    },

    onESCPress: function(promise) {
      if (this._isStillRequesting) {
        return promise.reject();
      }
      const model = this.getView().getModel();
      model.setProperty("/myItems", this._currentItems);
      return promise.resolve(); // continue closing the dialog
    },
    
    onAfterClose: function(event) {
      this.handleAfterClose(event.getSource(), this.byId("myList").getBinding("items"));
    },
    
    handleAfterClose: function(dialog, listBinding) {
      dialog.unbindElement(/*modelName*/); // <-- resets the data in dialog and cleans up its element binding infos internally. No dialog.destory needed.
      dialog.setBusy(false);
      listBinding.resume();
    },
    
    handleSubmit: function() {
      const dialog = this.byId("myDialog");
      if (!dialog.getBeginButton().getEnabled()) return;
      dialog.setBusy(true);
      if (!this._isStillRequesting) {
        this._isStillRequesting = true;
        /*Faking request:*/setTimeout(this.mySuccessHandler.bind(this), 3000)
      };
    },
    
    mySuccessHandler: function (newKeyFromServer = globalThis.crypto.randomUUID()) {
      const dialog = this.byId("myDialog");
      const context = dialog.getBindingContext();
      const value = context.getProperty("value");
      this._isStillRequesting = false;
      dialog.getModel().setProperty(context.getPath("key"), newKeyFromServer);
      dialog.close();
      sap.ui.require([ "sap/m/MessageToast" ], MT => MT.show(`${value} created`));
    },
  });

  const control = await XMLView.create({
    definition: document.getElementById("myxmlview").textContent,
    height: "100%",
    controller: new MyController(),
    models: {
      undefined: new JSONModel({
        "myItems": [],
      }),
      "messages": Core.getMessageManager().getMessageModel(),
    },
  });
  Core.getMessageManager().registerObject(control.placeAt("content"), true);
});
<script id="sap-ui-bootstrap" src="https://sdk.openui5.org/nightly/resources/sap-ui-core.js"
  data-sap-ui-oninit="onUI5Init"
  data-sap-ui-libs="sap.ui.core,sap.m,sap.ui.layout,sap.ui.unified"
  data-sap-ui-theme="sap_horizon"
  data-sap-ui-async="true"
  data-sap-ui-compatversion="edge"
  data-sap-ui-excludejquerycompat="true"
  data-sap-ui-resourceroots='{ "demo": "./" }'
  data-sap-ui-xx-waitForTheme="init"
></script>
<script id="myxmlview" type="text/xml">
  <mvc:View xmlns:mvc="sap.ui.core.mvc" height="100%" controllerName="demo.MyController">
    <App xmlns="sap.m">
      <Page backgroundDesign="List" title="Resetting inputs via client-side Model and Context">
        <headerContent>
          <Button id="addBtn" text="Add Item" type="Emphasized" press=".handleAddPress" />
        </headerContent>
        <List id="myList" growing="true" items="{
          path: '/myItems',
          key: 'key',
          templateShareable: false
        }">
          <StandardListItem title="{value}" info="Key: {key}"/>
        </List>
      </Page>
      <dependents>
        <Dialog id="myDialog"
          icon="sap-icon://ui-notifications"
          title="New Item"
          draggable="true"
          initialFocus="myInput"
          class="sapUiResponsiveContentPadding"
          escapeHandler=".onESCPress"
          afterClose=".onAfterClose"
        >
          <Input id="myInput"
            placeholder="&lt;New value>"
            valueLiveUpdate="true"
            value="{
              path: 'value',
              type: 'sap.ui.model.type.String',
              constraints: {
                minLength: 1
              }
            }"
            submit=".handleSubmit"
          />
          <beginButton>
            <Button
              text="Submit"
              type="Emphasized"
              enabled="{= !!%{value} &amp;&amp; !%{messages>/}.length}"
              press=".handleSubmit"
            />
          </beginButton>
        </Dialog>
      </dependents>
    </App>
  </mvc:View>
</script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>

Of course, binding and unbinding element also applies to server-side models such as the v2.ODataModel and v4.ODataModel. But how an instance of v2.Context/v4.Context can be created or accessed can differ depending on the use case. Refer to the documentation topics and API reference of the respective model, context, ODataListBinding, and ODataContextBinding.

Benefits of unbindElement/unbindObject with relative bindings

  • ✅ Control agnostic: no need to rely on control specific APIs such as myInput.setValue, mySwitch.setState, etc..
  • ✅ Reduced overload: no need to iterate over all existing controls. The framework will take care of resetting the relevant bindings.
  • ✅ Reduced maintenance: no need to maintain list of model properties that the application is supposed to reset manually.
  • ✅ Reduced workload: no need to call myDialog.destroy() every time just to clear the user inputs. Keep the existing cleaned up control instance and reuse it for the next data entry.
Boghyon Hoffmann
  • 17,103
  • 12
  • 72
  • 170
0

Bind all your control values to a model. Then reset this model after you've successfully saved the data.

Example:

control1.bindProperty("value", "/controlValues/control1Value"); // Binding
// control1.bindProperty("value", "/controlValues/name");
// <Input value="{/controlValues/name}" /> // <-- ideal binding in xml view

this.getView().getModel().setProperty("/controlValues", this.resetFormData()); // Clear Model

resetFormData: function () {
    var emptyControlValues = {
        "control1Value": "", // "name": "", <-- bind to control
        "control2Value": 0,  // "age": 0,
        "control3Value": "", // "address": "",
        "control4Value": ""  // "tel": ""
    };
    return emptyControlValues;
};
Antonette
  • 29
  • 5
  • thanks for the code snippet. This approach looks fine, but requires specifying all controllers/fields hard-coded, is there any way to avoid it and make code more generic? Thanks. – Mike Jul 08 '19 at 12:59
  • There is no hard-coding in this solution. It's called binding, you bind the value property when you create the control, the first line in my code was just for illustrative purposes. Read this post to understand how binding works: https://sapui5.hana.ondemand.com/1.34.9/docs/guide/91f0652b6f4d1014b6dd926db0e91070.html – Antonette Jul 08 '19 at 13:45
  • I know what you mean. I referenced that in your sample you have to enumerate `control1`, `control2`, etc. in `var emptyControlValues`. And I prefer to avoid writing it in every screen with input fields. – Mike Jul 08 '19 at 16:32
  • It doesn't have to be control1, control2... it could be your data i.e. name, surname, address etc. That's what a model is: the structure/mapping of your data – Antonette Jul 09 '19 at 13:07
  • You create the model once. You can bind multiple screens/controls to the same model. Whether you want to create or update an employee's address, you still map the data to the same model. Ultimately you use the same model to commit the data to the backend – Antonette Jul 09 '19 at 13:22