2

In a SAPUI5 application I would like to update the content of a control (e. g. a tile) only when this is currently visible to the user. I created a function like this:

updatePage: function() {
  for (var i = 0; i < this.myTiles.length; i++) {
    if (this.myTiles[i].updater) {
      this.myTiles[i].updater();
    }
  }
  setTimeout(this.updatePage.bind(this), 10000);
},

.. where the updater is a custom function I added to the tiles that is in charge to update their content.

The problem is: I want to check if the tile is currently visible to the user (i. e. is not in a page or in a tab that is not currently selected, but was rendered previously).
Is there a way to achieve this using object properties? Do I need to manage it manually?

Boghyon Hoffmann
  • 17,103
  • 12
  • 72
  • 170
LucaMus
  • 721
  • 7
  • 17
  • What is your target browser? If it's one of the modern browsers (except of Safari), you might want to use the [IntersectionObserver API](https://developers.google.com/web/updates/2016/04/intersectionobserver). Also check out [this video](https://youtu.be/kW_atFXMG98) – Boghyon Hoffmann Feb 01 '18 at 19:06
  • why not just `this.myTiles[i].getVisible()` or however you get the UI5 representation of your tile. I use that strategy all the time for form validation – Jorg Feb 02 '18 at 01:36
  • @Jorg: The visible state is not changed if the user changed page or active tab. – LucaMus Feb 02 '18 at 10:51
  • @boghyon: thanks for the very interesting hint. Unfortunately the application will be used also on iPad, so I'm worried this cannot be the proper solution. – LucaMus Feb 02 '18 at 10:52
  • @LucaMus The `IntersectionObserver` is now [supported by all major browsers](https://caniuse.com/#feat=intersectionobserver), including Safari. Would the answer https://stackoverflow.com/a/54038430/5846045 solve the issue? – Boghyon Hoffmann Apr 23 '20 at 16:38

2 Answers2

1

You can utilize jQuery do achieve that.

// determine whether your control is still part of the active DOM
jQuery.contains(document, yourControl.$());

// determine whether your control is visible (in terms of CSS)
yourControl.$().is(':visible');

// combined
MyControl.prototype.isVisible = function() {
  return this.$().length && // has rendered at all
    jQuery.contains(document, this.$()) && // is part of active DOM
    this.$().is(':visible'); // is visible
};

Elements could still be invisible to the user by being:

  • out of the viewport
  • visibility: hidden
  • opacity: 0
  • ...

Also check this answer

BR Chris

http://api.jquery.com/jQuery.contains/

http://api.jquery.com/visible-selector/

cschuff
  • 5,502
  • 7
  • 36
  • 52
  • This is a good answer overall. However, `jQuery.contains(document, this.$())` here you should use `this.$()[0]` because `Control.$()` returns jQuery object, while `jQuery.contains()` requires both arguments to be DOM elements instead of jQuery objects. It will always return false if the jQuery object is passed. I've made [an edit](https://stackoverflow.com/review/suggested-edits/22013982) but I doubt a random reviewer will approve this. – zypA13510 Jan 24 '19 at 03:35
1

The web API IntersectionObserver is now supported by all major browsers, including Safari.

Basic syntax

const myObserver = new IntersectionObserver(callback/*, settings?*/);
myObserver.observe(domElement);

Demo

Below is a demo with UI5. Run the snippet and try to scroll there. The page title changes depending on the visibility of the target element:

globalThis.onUI5Init = () => sap.ui.require([
  "sap/ui/core/mvc/XMLView",
  "sap/ui/model/json/JSONModel",
  "sap/ui/core/mvc/Controller",
], async (XMLView, JSONModel, Controller) => {
  "use strict";
  const control = await XMLView.create({
    definition: document.getElementById("myxmlview").textContent,
    models: new JSONModel({ "ratio": 0 }),
    controller: new (Controller.extend("demo.MyController", {
      onInit: function () {
        this.intersectionObserver = new IntersectionObserver(entries => {
          const targetEl = this.byId("myBox").getDomRef();
          const entry = entries.find(entry => entry.target === targetEl);
          const model = this.getView().getModel();
          model.setProperty("/ratio", entry && entry.intersectionRatio);
        });
      },
      onBeforeRendering: function () {
        this.intersectionObserver.disconnect();
      },
      onAfterRendering: function () {
        const targetEl = this.byId("myBox").getDomRef();
        this.intersectionObserver.observe(targetEl);
      },
      onExit: function () {
        this.intersectionObserver.disconnect();
        this.intersectionObserver = null;
      },
    }))(),
  });
  control.placeAt("content");
});
<script id="sap-ui-bootstrap"
  src="https://openui5.hana.ondemand.com/resources/sap-ui-core.js"
  data-sap-ui-libs="sap.ui.core,sap.m"
  data-sap-ui-async="true"
  data-sap-ui-theme="sap_horizon"
  data-sap-ui-oninit="onUI5Init"
  data-sap-ui-resourceroots='{ "demo": "./" }'
  data-sap-ui-compatVersion="edge"
  data-sap-ui-excludejquerycompat="true"
  data-sap-ui-xx-waitForTheme="init"
></script>
<script id="myxmlview" type="text/xml">
  <mvc:View controllerName="demo.MyController"
    xmlns:mvc="sap.ui.core.mvc"
    xmlns:core="sap.ui.core"
    xmlns="sap.m"
    displayBlock="true"> 
    <App>
      <Page title="Tile visible: {= ${/ratio} > 0}">
        <FlexBox renderType="Bare"
          height="360vh"
          justifyContent="Center"
          alignItems="Center">
          <core:Icon id="myBox"
            src="sap-icon://color-fill"
            size="5rem"
            color="Critical"/>
        </FlexBox>
      </Page>
    </App>
  </mvc:View>
</script>
<body id="content" class="sapUiBody sapUiSizeCompact"></body>
Boghyon Hoffmann
  • 17,103
  • 12
  • 72
  • 170