čtvrtek 12. prosince 2013

Implementace události VisibilityChanged v javascriptu

Potřebovali jste někdy dostat informaci ve chvíli, kdy se změní viditelnost nějakého prvku na stránce? Já k podobnému problému došel zrovna nedávno a nenašel žádné vhodné řešení, tak s pomocí pár tipů na fóru stackoverflow nabízím vlastní.

Budeme potřebovat knihovnu jquery (v příkladu používám verzi 1.10.2) a její šikovný selector ":visible". Tento selektor umí rozpoznat jestli element, ke kterému se váže je skutečně vidět - tzn. není to je tupá kontrola css-kové vlastnost visibility, ale projde si i všechny rodičovské elementy v DOMu, jestli náhodou nemá vidtelnost vypnutou některý z nich.

Následně budeme kontrolovat v pravidelném intervalu stav daného elementu (nebo více elementů), aktuální hodnotu viditelnosti si uložíme do "data-" atributu a v jakmile dojde ke změně vystřelíme událost.

Vytvoříme si obslužnou třídu VisibilityChangedObserver, která bude mít public metody
  1. register($selector, callback)
    - zaregistruje událost visibilityChanged na zadaném prvku/prvcích dle použitého selektoru a zavolá callback s parametrem event (jquery event object), který v propertě result nese booleanovou hodnotu jestli je cílová element viditelný
  2. deregister($selector)
    - odregistruje událost visibilityChanged na zadaném prvku/prvcích
  3. start(interval = 100)
    - sputí smyčku sledování a kontroluje stav v pravidelných intervalech 9pokud není zadáno tak po 100 milisekundách)
  4. stop()
    - ukončí smyčku sledování
Celá třída pak vypadá následovně:

var VisibilityChangedObserver = function () {
    var registrations = [];
    var timerId = null;
    var self = this;

    var checkVisiblities = function() {
        "use strict";
        for (var i = 0; i < registrations.length; i++) {
            var registration = registrations[i];
            var wasVisible = $(registration).data("visibilityChangedPrevious") == "visible";
            var isVisible = $(registration).is(":visible");

            if (wasVisible != isVisible) {
                $(registration).data("visibilityChangedPrevious", isVisible ? "visible" : "hidden");
                $(registration).trigger("visibilityChanged");
            }
        }
    };

    this.register = function($selector, callback) {
        "use strict";
        $selector.each(function() {
            registrations.push(this);
            $(this).on("visibilityChanged", function(event) {
                event.result = $(this).is(":visible");
                callback(event);
            });
        });
        return this;
    };

    this.deregister = function($selector) {
        "use strict";
        var index = registrations.indexOf($selector);
        if (index > -1) {
            registrations = registrations.splice(index, 1);
        }
    };

    this.start = function() {
        "use strict";
        timerId = window.setInterval(checkVisiblities, 100);
        return this;
    };

    this.stop = function() {
        "use strict";
        if (timerId != null) {
            window.clearInterval(timerId);
        }
        return this;
    };
};

V následujícím příkladu si můžete uvedenou funkcionalitu vyzkoušet. Spustitelné demo na http://jsfiddle.net/8gPq6/. Čtyři tlačítka v headeru zobrazují/skrývají odpovídající elementy, do patičky pak právě pomocí nové události přicházejí zprávy o viditelnosti.


<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Sample</title>
<link rel="stylesheet" type="text/css" href="site.css" />
<script type="text/javascript" src="jquery-1.10.2.min.js"></script>
<script type="text/javascript" src="visibilityChangedObserver.js"></script>
<script type="text/javascript">

var messageStack = [];

var updateStatusMessage = function(message){
 messageStack.push(message);
 while (messageStack.length > 3) {
  messageStack.shift();
 }
 $("footer").html("<p>" + messageStack.join("<br />") + "</p>");
};

$(document).ready(
 function(){
  $("button").on("click", function(event){
   var asociated = $(this).data("asociated");
   $(asociated).toggle();
  });

  var observer = new VisibilityChangedObserver()
   .register($(".visibilityCheck"), function(event){
    var message = "Element with #id = '" + $(event.target).attr("id") + "' is " + ((event.result) ? "visible." : "hidden.");
    updateStatusMessage(message);
   })
   .start();
 }
);


</script>
</head>
<body>

<header>
 <button id="btnOuter" data-asociated="#outer">Toggle outer</button>
 <button id="btnInner1" data-asociated="#inner1">Toggle inner1</button>
 <button id="btnInner2" data-asociated="#inner2">Toggle inner2</button>
 <button id="btnInner3" data-asociated="#inner3">Toggle inner3</button>
</header>

<div id="main">

 <div id="outer">
  <p>Outer DIV</p>
  <div id="inner1" class="inner visibilityCheck">
   <p>Inner DIV1</p>
  </div>
  <div id="inner2" class="inner visibilityCheck">
   <p>Inner DIV2</p>
  </div>
  <div id="inner3" class="inner visibilityCheck">
   <p>Inner DIV3</p>
  </div>
 </div>

</div>

<footer>
</footer>
</body>
</html>