Mike
Mike

Reputation: 14586

How to reset bound control values efficiently?

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?

Upvotes: 2

Views: 4036

Answers (3)

Boghyon Hoffmann
Boghyon Hoffmann

Reputation: 18044

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.

Upvotes: 1

matbtt
matbtt

Reputation: 4231

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": ""
    });
}

Upvotes: 1

Antonette
Antonette

Reputation: 29

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;
};

Upvotes: 0

Related Questions