Friday, August 16, 2013

Memahami `DOMContentLoaded`

DOM Ready… kebanyakan orang menyalahartikannya dengan DOM Loaded, padahal DOM Ready itu tidak sama dengan DOM Loaded. DOM Loaded atau onload adalah sebuah jenis event dalam JavaScript yang akan terpicu ketika seluruh halaman dan muatan yang ditransfer telah berhasil dimuat. Atau bahasa mudahnya adalah ketika indikator memuat pada peramban telah menghilang (atau berhenti berputar-putar, tergantung desain antarmuka peramban):

Indikator memuat pada peramban sedang tampil
Indikator memuat pada peramban.

Sedangkan DOM Ready atau DOMContentLoaded adalah event yang akan terpicu ketika peramban telah berhasil membaca keseluruhan elemen DOM, yaitu dari bagian awal halaman web sampai ke akhir halaman. Dari elemen <html> sampai elemen penutupnya yaitu </html>, tapi tidak tergantung pada apakah semua muatan telah berhasil terpanggil atau tidak. Selama peramban telah berhasil membaca elemen HTML dari awal halaman sampai akhir halaman, itu saja sudah cukup untuk memicu event ini:

// Event `DOMContentLoaded` pada peramban-peramban moderen
window.addEventListener("DOMContentLoaded", function() {}, false);

// Event `onload` (hanya bekerja jika seluruh muatan halaman telah berhasil termuat)
window.onload = function() {};

Sebuah penerapan DOMContentLoaded dapat digambarkan sebagai berikut:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Test</title>

    <script>
      window.addEventListener("DOMContentLoaded", function() {
        document.querySelector('#test-elem').style.border = "5px solid red";
      }, false);
    </script>

  </head>
  <body>

    <div id="test-elem">Test</div>

  </body>
</html>

Berdasarkan contoh di atas, sebuah elemen #test-elem seharusnya akan dikenai border berwarna merah setebal 5 piksel, namun tidak jika keadaannya seperti ini. Justru, keadaan ini malah akan menampilkan konsol error karena JavaScript tidak berhasil menemukan elemen yang dimaksud:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Test</title>

    <script>
      document.querySelector('#test-elem').style.border = "5px solid red";
      // TypeError: document.querySelector("#test-elem") is null
    </script>

  </head>
  <body>

    <div id="test-elem">Test</div>

  </body>
</html>

Berbeda dengan , JavaScript hanya akan berhasil menyeleksi elemen jika elemen tersebut telah ada. Memicu fungsi untuk memberikan border pada elemen sebelum elemen tersebut berhasil terbaca tidak akan menghasilkan apapun. Untuk mengatasi masalah di atas, maka Anda cukup memastikan saja bahwa elemen yang ingin diseleksi telah terbaca oleh peramban sebelum JavaScript terpicu:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Test</title>
  </head>
  <body>

    <div id="test-elem">Test</div>

    <script>
      document.querySelector('#test-elem').style.border = "5px solid red";
    </script>

  </body>
</html>

Akan tetapi dalam keadaan tertentu, Anda mungkin tidak bisa mengkondisikan JavaScript dengan posisi seperti di atas (mungkin masalah CMS yang tidak mengizinkan, alasan efisiensi dengan cara menyatukan semua file JavaScript ke dalam satu file berukuran besar, atau karena hal yang lain). Jika memang karena suatu alasan Anda hanya bisa meletakkan JavaScript di area <head>, maka event DOMContentLoaded bisa menjadi solusi. Namun, mengingat event ini hanya diadakan pada peramban-peramban moderen, Anda perlu membuat fungsi penanganan untuk membuat event pengganti seperti event ini jika peramban yang digunakan tidak dilengkapi dengan event DOMContentLoaded. Beberapa sudah ada yang membuatnya, misalnya ini atau ini.

Pengguna JavaScript Library

Para pengguna perpustakaan JavaScript sepertinya tidak perlu begitu memikirkan mengenai ini karena sebagian besar library sudah dilengkapi dengan API untuk menangani event DOMContentLoaded:

Jika Memang Tidak Harus

Jika Anda merasa tidak harus menggunakan event ini, maka cara yang paling praktis, aman, sesuai prosedur dan bisa bekerja pada semua peramban adalah cukup dengan meletakkan JavaScript sedekat mungkin dengan bagian akhir halaman, yaitu ketika tidak ada elemen HTML lagi di bawahnya kecuali tag penutup </body>. Hasilnya tidak jauh berbeda dengan DOMContentLoaded karena pada dasarnya keduanya akan memicu fungsi pada waktu yang hampir sama (hanya berbeda tipis):

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Test</title>
  </head>
  <body>

    <div>Test</div>
    <div>Test</div>
    <div>Test</div>
    <div>Test</div>
    <div>Test</div>
    ...

    <script>
      alert('DOM is (almost) ready!');
    </script>

  </body>
</html>

Atau buat fungsi utama pada area <head> kemudian jalankan di bagian akhir halaman:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Test</title>

    <script>
      function myGlobalFunction() {
        alert('DOM is (almost) ready!');
      }
    </script>

  </head>
  <body>

    <div>Test</div>
    <div>Test</div>
    <div>Test</div>
    <div>Test</div>
    <div>Test</div>
    ...

    <script>myGlobalFunction();</script>

  </body>
</html>

Referensi Lain

Labels: ,

Sunday, August 4, 2013

JQuery Find and Highlight Text

HTML, CSS & jQuery

<!DOCTYPE html>
<html dir="ltr">
<head>
<meta charset="utf-8">
<title>jQuery Find and Highlight Text</title>
<style>
body {
  background-color:white;
  padding:40px 10px 10px;
  font:normal normal 13px/1.4 Arial,Sans-Serif;
  color:black;
}
.text-finder {
  position:fixed;
  top:0;
  right:0;
  left:0;
  z-index:999;
  background-color:white;
  border-bottom:1px solid gray;
  padding:5px 6px;
  box-shadow:0 1px 2px rgba(0,0,0,.4);
}
.highlight {
  background-color:yellow; /* highlight color */
  font-weight:bold;
}
</style>
<script src="//code.jquery.com/jquery-2.0.2.js"></script>
<script>
/*!
 highlight v4
 Highlights arbitrary terms.
 <http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html>
 MIT license.
 Johann Burkard
 <http://johannburkard.de>
 <mailto:jb@eaio.com>
*/
(function($) {
    $.fn.highlight = function(pat) {
        function innerHighlight(node, pat) {
            var skip = 0;
            if (node.nodeType == 3) {
                var pos = node.data.toUpperCase().indexOf(pat);
                if (pos >= 0) {
                    var spannode = document.createElement('span');
                    spannode.className = 'highlight';
                    var middlebit = node.splitText(pos);
                    var endbit = middlebit.splitText(pat.length);
                    var middleclone = middlebit.cloneNode(true);
                    spannode.appendChild(middleclone);
                    middlebit.parentNode.replaceChild(spannode, middlebit);
                    skip = 1;
                }
            } else if (node.nodeType == 1 && node.childNodes && !/(script|style)/i.test(node.tagName)) {
                for (var i = 0; i < node.childNodes.length; ++i) {
                    i += innerHighlight(node.childNodes[i], pat);
                }
            }
            return skip;
        }
        return this.length && pat && pat.length ? this.each(function() {
            innerHighlight(this, pat.toUpperCase());
        }) : this;
    };
    $.fn.removeHighlight = function() {
        return this.find("span.highlight").each(function() {
            this.parentNode.firstChild.nodeName;
            with (this.parentNode) {
                replaceChild(this.firstChild, this);
                normalize();
            }
        }).end();
    };
})(jQuery);

// Text finder form function
(function($) {
    $(document).ready(function() {
        var $finder = $('#text-finder'),
            $field = $finder.children().first(),
            $clear = $field.next(),
            $area = $(document.body),
            $viewport = $('html, body');
        $field.on("keyup", function() {
            $area.removeHighlight().highlight(this.value); // Highlight text inside `$area` on keyup
            $viewport.scrollTop($area.find('span.highlight').first().offset().top - 50); // Jump the viewport to the first highlighted term
        });
        $clear.on("click", function() {
            $area.removeHighlight(); // Remove all highlight inside `$area`
            $field.val('').trigger("focus"); // Clear the search field
            $viewport.scrollTop(0); // Jump the viewport to the top
            return false;
        });
    });
})(jQuery);
</script>
</head>
<body>

<form id="text-finder" class="text-finder" action="javascript:;">
    <input type="text" placeholder="Find...">
    <input type="reset" value="&times;">
</form>

Content goes here...

</body>
</html>

Lihat Demo

Labels: , , ,