<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Demo</title>
    <script id="sap-ui-bootstrap"
      src="https://sdk.openui5.org/resources/sap-ui-core.js"
      data-sap-ui-libs="sap.ui.core,sap.m"
      data-sap-ui-onInit="onUI5Init"
      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>
      globalThis.onUI5Init = () => sap.ui.require([
        "sap/ui/core/mvc/XMLView",
        "sap/ui/model/json/JSONModel", // also works with ODataModel
        "sap/ui/core/mvc/Controller",
      ], async (XMLView, JSONModel, Controller) => {
        "use strict";

        const control = await XMLView.create({
          definition: `<mvc:View xmlns:mvc="sap.ui.core.mvc">
            <App xmlns="sap.m">
              <Page showHeader="false" binding="{/0}" class="sapUiResponsiveContentPadding">
                <my:CheckPrice xmlns:my="demo.controls" valuePrice="{Price}" />
                <Button text="Update Price" press=".onPress" />
              </Page>
            </App>
          </mvc:View>`,
          height: "100%",
          models: new JSONModel([
            {
              Price: 123
            }
          ]),
          controller: new (Controller.extend("demo.MyController", {
            onPress: (event) => {
              const context = event.getSource().getBindingContext();
              const pricePath = context.getPath("Price");
              const currentPrice = context.getProperty("Price");
              context.getModel().setProperty(pricePath, currentPrice + 1);
            },
          }))(),
        });

        control.placeAt("content");
      });
    </script>
  </head>
  <body class="sapUiBody" id="content"></body>
</html>
sap.ui.define([
  "sap/ui/core/Control",
], function (Control) {
  "use strict";

  return Control.extend("demo.controls.CheckPrice", {
    metadata: {
      properties: {
        "valuePrice": {}, // type: "string" by default
      },
      // ...
    },

    init: function () {
      Control.prototype.init.apply(this, arguments); // Call super method first
      // ...
    },

    renderer: {
      apiVersion: 2, // In-place DOM patching aka. semantic rendering since 1.67
      render: function (oRm, oControl) {
        oRm.openStart("div", oControl).class("zgutCheckPriceWrap").openEnd()
          .openStart("span").class("zgutPriceWrap").openEnd()
            .text(oControl.getValuePrice()) // Already escapes user content
          .close("span")
          // .renderControl(...)
        .close("div");
      },

      // Legacy string-based rendering - these APIs are DEPRECATED since 1.92
      _render: function (oRm, oControl) { // Remove "apiVersion" and "_" to try
        oRm.write("<div").writeControlData(oControl)
          .addClass("zgutCheckPriceWrap").writeClasses().write(">")
          .write("<span").addClass("zgutPriceWrap").writeClasses().write(">")
            .writeEscaped(oControl.getValuePrice()) // User content needs escape
          .write("</span>")
          // .renderControl(...)
        .write("</div>");
      },
    },

    // ...
  });
});