Minggu, 06 Oktober 2013

Efek Masonry Hanya dengan CSS

CSS3 Masonry Layout

Teknik ini sudah pernah dibahas dengan cukup detail di CSS-Tricks. Hanya saja Saya tidak habis pikir mengapa beliau tetap mempertahankan deklarasi column-count untuk memecah area menjadi beberapa kolom dan menggunakan media queries untuk mengurangi jumlah kolom pada saat ukuran layar peramban menyempit. Padahal jika kita sudah menentukan lebar masing-masing item dengan ukuran yang sama, kita bisa menggunakan column-width untuk menentukan lebar kolom tetap tanpa harus melibatkan media queries untuk mengurangi jumlah kolom pada saat ukuran layar semakin sempit. column-width akan menciptakan kolom-kolom dengan jumlah yang bisa menyesuaikan diri berdasarkan ruang yang tersisa. Sudah tertulis dengan jelas dalam spesifikasi:

.container {
column-width:150px;
column-gap:5px; /* Margin kanan/kiri antarkolom */
}

img {
display:block;
width:100%;
height:auto;
margin:0 0 5px 0; /* Margin bawah antargambar */
}

Item Bukan Gambar

Ada satu hal yang harus diperhatikan jika Anda ingin menciptakan efek/tata letak seperti ini pada elemen yang bukan merupakan gambar, yaitu deklarasi display berupa inline-block. Mendeklarasikan perintah ini akan mencegah perpotongan yang tak terduga pada masing-masing item karena CSS3 Kolom secara normal akan berusaha untuk membuat masing-masing kolom menjadi sama tinggi. Beberapa harus terpaksa dipotong di bagian bawah mengingat properti CSS ini memang sebenarnya berbasis teks:

.container {
column-width:150px;
column-gap:5px; /* Margin kanan/kiri antarkolom */
}

.item {
display:inline-block; /* Mencegah pemotongan item yang tak terduga */
margin:0 0 5px 0; /* Margin bawah antaritem */
padding:10px;
background-color:black;
color:white;
}

Ini yang Saya maksud sebagai perpotongan yang tidak diduga dan tidak dikehendaki:

Item terpotong pada akhir kolom pada tempat yang salah.
Teks terpotong pada bagian bawah untuk memastikan agar masing-masing kolom memiliki tinggi yang sama.

Tidak tahu apa itu Masonry?

Sabtu, 05 Oktober 2013

JavaScript Table Sorter

Skrip ini bisa digunakan untuk menyortir tabel dengan tampilan yang sederhana:

JavaScript

// Original code: http://stackoverflow.com/a/14268260/1163000
// Usage: `makeSortable(elem);`
(function() {
function sortTable(table, col, reverse) {
var tb = table.tBodies[0], // Use `<tbody>` to ignore `<thead>` and `<tfoot>` rows
tr = Array.prototype.slice.call(tb.rows, 0); // Put rows into array
reverse = -((+reverse) || -1);
tr = tr.sort(function (a, b) { // Sort rows
return reverse * (a.cells[col].textContent.trim().localeCompare(b.cells[col].textContent.trim()));
});
for (var i = 0, len = tr.length; i < len; ++i) {
tb.appendChild(tr[i]); // Append each row in order
}
}
function makeSortable(table) {
var th = table.tHead, i;
th && (th = th.rows[0]) && (th = th.cells);
if (th) {
i = th.length;
} else {
return; // If no `<thead>` then do nothing
}
while (--i >= 0) (function(i) {
var dir = 1;
th[i].onmousedown = function() {
for (var j = 0, jen = th.length; j < jen; ++j) {
th[j].className = th[j].className.replace(/(^| )desc|asc( |$)/g, "$1$2");
}
sortTable(table, i, (dir = 1 - dir));
this.className += dir === 1 ? " desc" : " asc";
return false;
};
}(i));
}
window.makeSortable = makeSortable;
})();

CSS

.table-sortable {
border-collapse:collapse;
table-layout:fixed;
width:100%;
font:normal normal 13px/1.4 Arial,Sans-Serif;
color:black;
background-color:white;
}

.table-sortable th,
.table-sortable td {
margin:0;
padding:5px 8px;
border:none;
vertical-align:top;
text-align:left;
}

.table-sortable th {
font-weight:bold;
background-color:slategray;
color:white;
border-color:white;
}

.table-sortable tbody tr:nth-child(even) {background-color:whitesmoke}
.table-sortable th {cursor:pointer}

th.asc,
th.desc {background-color:lightslategray}

th.asc:before,
th.desc:before {
content:"";
display:block;
float:right;
width:0;
height:0;
border:.4em solid transparent;
}

th.asc:before {
border-bottom-color:inherit;
border-top-width:0;
margin-top:.4em;
}

th.desc:before {
border-top-color:inherit;
border-bottom-width:0;
margin-top:.5em;
}

HTML

<table class="table-sortable" id="table-1">
<thead>
<tr><th>Header 1</th><th>Header 2</th><th>Header 3</th></tr>
</thead>
<tbody>
<!-- Sortable rows -->
<tr><td> ... </td><td> ... </td><td> ... </td></tr>
<tr><td> ... </td><td> ... </td><td> ... </td></tr>
<tr><td> ... </td><td> ... </td><td> ... </td></tr>
...
</tbody>
</table>
<script>makeSortable(document.getElementById('table-1'));</script>

Meski masih ada sedikit kekurangan karena fungsi sortir ini hanya memakai logika berdasarkan abjad. Coba Anda klik pada header Harga. Hasilnya tidak akan sesuai dengan apa yang kita harapkan. Masih sedang mencari solusi yang paling mudah untuk menangani satuan harga pada fungsi ini…

Demo

Senin, 30 September 2013

JavaScript Image Trail Tooltip

SXC Browse Page
Halaman browse foto di SXC.

Pertama kali Saya melihat tooltip semacam ini di situs SXC. Saya mencoba untuk membuat replikanya dengan tanpa menyertakan dukungan untuk peramban-peramban lawas. document.all itu sudah sangat ketinggalan zaman!

Keistimewaan dari tooltip ini adalah dia bisa bergerak mengikuti gerakan pointer serta bisa mempertahankan posisinya agar tetap berada pada area yang terlihat di halaman agar Anda tidak mengalami masalah-masalah seperti: tooltip keluar terlalu jauh ke sebelah kanan atau ke sebelah bawah dari area terlihat pada halaman. JavaScript ini juga bisa digunakan untuk menggantikan salah satu plugin JQuery Saya yang tidak begitu terkenal dan tidak sering diperbaharui lagi:

JavaScript

/*! JavaScript Image Trail Tooltip by Taufik Nurrohman <http://gplus.to/tovic> */
(function(w, d) {

var tooltip = d.createElement('div'),
noImage = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", // 1 x 1 pixel transparent GIF
top = 0,
left = 0,
docWidth = 0,
docHeight = 0,
offsetTop = 20, // Default top distance of the tooltip to the mouse pointer
offsetLeft = 20, // Default left distance of the tooltip to the mouse pointer
wait = null;

// Get the correct width of the document without scrollbars
function getDocWidth() {
return d.documentElement.clientWidth;
}

// Get the correct height of the document
function getDocHeight() {
return Math.max(
d.body.scrollHeight, d.documentElement.scrollHeight,
d.body.offsetHeight, d.documentElement.offsetHeight,
d.body.clientHeight, d.documentElement.clientHeight
);
}

tooltip.id = "trail-image";
tooltip.className = "trail-image";
tooltip.innerHTML = '<img src="' + noImage + '" alt="Loading..." style="float:none;display:block;width:100%;height:100%;max-width:none;max-height:none;min-width:0;min-height:0;border:none;outline:none;background:none;margin:0;padding:0;">';

// Just like `DOMContentLoaded` event, but only to
// wait for the existence of the `<body>` element
// to insert the tooltip markup in the proper area
function waitForBodyExist() {
if (!d.body) {
wait = setTimeout(waitForBodyExist, 100);
} else {
clearTimeout(wait);
d.body.appendChild(tooltip);
docWidth = getDocWidth();
docHeight = getDocHeight();
w.onresize = function() {
docWidth = getDocWidth();
docHeight = getDocHeight();
};
w.onscroll = hideTrail;
}
// console.log('Still waiting...');
} waitForBodyExist();

// Function to show the tooltip
// `width` => the tooltip width
// `height` => the tooltip height
// `file` => the URL of the image to show
function showTrail(width, height, file) {
tooltip.style.visibility = "visible";
tooltip.children[0].src = file;
tooltip.style.width = parseInt(width, 10) + "px";
tooltip.style.height = parseInt(height, 10) + "px";
d.onmousemove = function(e) {
if (!e) e = w.event;
if (e.pageX || e.pageY) {
left = e.pageX;
top = e.pageY;
} else if (e.clientX || e.clientY) {
left = e.clientX + d.body.scrollLeft + d.documentElement.scrollLeft;
top = e.clientY + d.body.scrollTop + d.documentElement.scrollTop;
}
tooltip.style.top = parseInt(((top >= docHeight - (height + offsetTop + 10)) ? top - (height + offsetTop) : top + offsetTop), 10) + "px";
tooltip.style.left = parseInt(((left >= docWidth - (width + offsetLeft + 10)) ? left - (width + offsetLeft) : left + offsetLeft), 10) + "px";
};
}

// Function to hide the tooltip
function hideTrail() {
d.onmousemove = "";
tooltip.style.top = "-9999px";
tooltip.style.left = "-9999px";
tooltip.style.visibility = "hidden";
tooltip.children[0].src = noImage;
docWidth = getDocWidth();
docHeight = getDocHeight();
}

// Add to the window object as an external/global function
w.showTrail = showTrail;
w.hideTrail = hideTrail;

})(window, document);

CSS

/* Image Trail Tooltip CSS */
.trail-image {
width:0;
height:0;
background-color:#ddd;
border:1px solid #888;
position:absolute;
top:-9999px;
left:-9999px;
z-index:9999;
visibility:hidden;
-webkit-box-shadow:0 1px 1px rgba(0,0,0,.2);
-moz-box-shadow:0 1px 1px rgba(0,0,0,.2);
box-shadow:0 1px 1px rgba(0,0,0,.2);
}

Penggunaan

Dasar Penggunaan

Ada dua fungsi utama dalam skrip di atas, yaitu showTrail dan hideTrail. showTrail digunakan untuk menampilkan tooltip sedangkan hideTrail digunakan untuk menyembunyikannya. Anda bisa menerapkannya pada atribut onmouseover dan onmouseout seperti ini:

<a href="img/large.jpg">
<img onmouseover="showTrail(250, 100, &#39;img/medium.jpg&#39;);" onmouseout="hideTrail();" src="img/small.jpg" alt="">
</a>

Tentukan lebar dan tinggi gambar baru pada parameter width dan height serta URL gambar baru yang ingin ditampilkan pada parameter file:

Menyimpan Data di dalam Atribut HTML

Menuliskan fungsi berkali-kali secara inline mungkin akan melelahkan. Untuk itu Anda bisa memodifikasinya dengan cara memasukkan setiap URL gambar yang lebih besar di dalam atribut HTML5 data-*:

<img alt="" src="img/small.jpg" data-image-preview="img/medium.jpg">

Kemudian untuk penerapan fungsinya:

(function() {
var img = document.getElementsByTagName('img');
for (var i = 0, len = img.length; i < len; ++i) {
if (img[i].getAttribute('data-image-preview')) {
img[i].onmouseover = function() {
showTrail(this.offsetWidth * 3, this.offsetHeight * 3, this.getAttribute('data-image-preview'));
};
img[i].onmouseout = hideTrail;
}
}
})();

Pada kode di atas Saya menerapkan this.offsetWidth * 3 dan this.offsetHeight * 3 untuk menciptakan skala tooltip menjadi tiga kali lipat dari gambar thumbnail yang didekati pointer. Jadi anggap saja URL yang berada di dalam atribut data-image-preview memiliki ukuran sebesar tiga kali lipat dari thumbnail.

Pastikan DOM sudah berada dalam keadaan siap:

Tip untuk Gambar-Gambar dari Picasa

Kita tahu bahwa kita bisa mengubah resolusi gambar yang disimpan di Picasa dengan cara mengubah path /sN pada setiap gambar. Berikut ini adalah contoh penerapan fungsi showTrail dan hideTrail tanpa memerlukan adanya atribut data-image-preview. Sekali Anda menerapkan fungsi ini, maka semua gambar yang berasal dari Picasa dengan ukuran lebar tidak lebih dari 100 piksel akan bisa menampilkan tooltip dengan lebar gambar 400 piksel:

(function() {
var img = document.getElementsByTagName('img');
for (var i = 0, len = img.length; i < len; ++i) {
var valid = (img[i].src && /\/s[0-9]+(\-c)?\/.*?\.(bmp|gif|jpg|jpeg|png)/i.test(img[i].src) && img[i].offsetWidth <= 100);
if (valid) {
img[i].onmouseover = function() {
showTrail(this.offsetWidth * 4, this.offsetHeight * 4, this.src.replace(/\/s[0-9]+(\-c)?/, "/s400$1"));
};
img[i].onmouseout = hideTrail;
}
}
})();

Pembaharuan: Pengecekan lebar gambar yang Saya terapkan di atas terkadang tidak bekerja karena mungkin fungsi telah tereksekusi sebelum dimensi gambar terbentuk, sehingga lebar gambar akan sama dengan 0 dan itu membuatnya bisa lolos dari pengecekan. Anda bisa mengeluarkan logika img[i].offsetWidth <= 100 dan memasukkannya ke dalam event onmouseover seperti ini untuk memastikan agar pengecekan lebar gambar bisa dilakukan setelah dimensi gambar terbentuk setiap kali Anda mendekatkan pointer ke atas gambar:

var valid = (img[i].src && /\/s[0-9]+(\-c)?\/.*?\.(bmp|gif|jpg|jpeg|png)/i.test(img[i].src));
if (valid) {
img[i].onmouseover = function() {
if (this.offsetWidth <= 100) {
showTrail(this.offsetWidth * 4, this.offsetHeight * 4, this.src.replace(/\/s[0-9]+(\-c)?/, "/s400$1"));
}
};
img[i].onmouseout = hideTrail;
}

Catatan Tambahan untuk Internet Explorer

Pastikan deklarasi <!DOCTYPE html> dinyatakan pada dokumen HTML Anda agar skrip ini bisa bekerja dengan baik. Selain itu, menambahkan tag meta ini sedekat mungkin dengan <head> juga bisa mencegah Internet Explorer untuk berubah ke mode compatibility view tanpa kita kehendaki:

<meta http-equiv='X-UA-Compatible' content='IE=Edge'>

Sabtu, 28 September 2013

Memposisikan Judul dan Ringkasan Posting di Samping Thumbnail atau Thumbnail di atas Judul dan Ringkasan Posting

Beberapa orang bertanya mengenai bagaimana caranya memposisikan judul dan ringkasan posting di samping thumbnail, atau mengenai bagaimana cara memposisikan thumbnail di atas judul dan ringkasan posting (biasanya untuk membuat templat List & Masonry Grid). Di sini Saya mencoba untuk membuat kode XML posting khusus yang Saya buat sefleksibel mungkin untuk keperluan modifikasi di masa datang. Kode ini sudah mencakup modifikasi ringkasan posting, sehingga jika Anda sudah mengimplementasikan modifikasi ini, maka Anda tidak perlu lagi repot-repot untuk mengimplementasikan tutorial pembuatan posting auto read-more.

Namun sebelum melakukan modifikasi ini, perlu dipertimbangkan bahwa nanti Anda akan mengubah struktur posting secara keseluruhan, sehingga beberapa kustomisasi lama Anda seperti penambahan widget artikel terkait, widget berbagi dan semuanya akan menghilang. Disarankan untuk menyimpan salinan templat Anda terlebih dahulu sebelum melakukan modifikasi ini.

Kode XML

Langkah pertama adalah mengedit XML templat. Kode XML ini cukup diterapkan sekali saja. Mengenai pengaturan posisi setiap elemen bisa kita lakukan dengan CSS yang sudah Saya tuliskan di bawah:

<b:includable id='post' var='post'>

<article expr:class='&quot;_post _post-&quot; + data:blog.pageType' expr:id='&quot;post-&quot; + data:post.id'>

<b:if cond='data:blog.pageType != &quot;item&quot;'>
<b:if cond='data:blog.pageType != &quot;static_page&quot;'>
<div class='_post-part _post-thumbnail-area'>
<b:if cond='data:post.thumbnailUrl'>
<img class='_post-thumbnail' expr:alt='data:post.title' expr:src='data:post.thumbnailUrl' height='72' width='72'/>
</b:if>
</div> <!-- ._post-thumbnail-area -->
</b:if>
</b:if>

<div class='_post-part _post-body-area'>

<h3 class='_post-title'>
<b:if cond='data:post.title'>
<b:if cond='data:post.link'>
<a expr:href='data:post.link'><data:post.title/></a>
<b:else/>
<b:if cond='data:post.url'>
<b:if cond='data:blog.url != data:post.url'>
<a expr:href='data:post.url'><data:post.title/></a>
<b:else/>
<data:post.title/>
</b:if>
<b:else/>
<data:post.title/>
</b:if>
</b:if>
<b:else/>
[Untitled]
</b:if>
</h3> <!-- ._post-title -->

<div class='_post-body'>
<b:if cond='data:blog.pageType == &quot;item&quot;'>
<data:post.body/>
<b:else/>
<b:if cond='data:blog.pageType == &quot;static_page&quot;'>
<data:post.body/>
<b:else/>
<b:if cond='data:post.snippet'>
<data:post.snippet/>
<b:else/>
No content.
</b:if>
</b:if>
</b:if>
</div> <!-- ._post-body -->

<footer class='_post-footer'>
<div class='_post-footer-line _post-footer-line-1'>
<span class='_post-author vcard'>
<b:if cond='data:top.showAuthor'>
<data:top.authorLabel/> <span class='fn'>
<b:if cond='data:post.authorProfileUrl'>
<a class='g-profile' expr:href='data:post.authorProfileUrl' rel='author' title='Author Profile'><data:post.author/></a>
<b:else/>
<span class='g-profile'><data:post.author/></span>
</b:if>
</span>
</b:if>
</span> <span class='_post-timestamp'>
<b:if cond='data:top.showTimestamp'>
<data:top.timestampLabel/> <a class='_post-timestamp-link' expr:href='data:post.url' rel='bookmark' title='Permanent Link'><time class='_post-published published' expr:datetime='data:post.timestampISO8601'><data:post.timestamp/></time></a>
</b:if>
</span> <span class='_post-comment-link'>
<b:if cond='data:blog.pageType != &quot;item&quot;'>
<b:if cond='data:blog.pageType != &quot;static_page&quot;'>
<b:if cond='data:post.allowComments'>
<a expr:href='data:post.url + &quot;#comments&quot;'><data:post.commentLabelFull/></a>
</b:if>
</b:if>
</b:if>
</span>
</div> <!-- ._post-footer-line-1 -->
<div class='_post-footer-line _post-footer-line-2'>
<span class='_post-labels'>
<b:if cond='data:post.labels'>
<data:postLabelsLabel/> <b:loop values='data:post.labels' var='label'>
<a expr:href='data:label.url' rel='tag'><data:label.name/></a><b:if cond='data:label.isLast != &quot;true&quot;'>, </b:if>
</b:loop>
</b:if>
<!-- b:include data='post' name='postQuickEdit'/ -->
</span>
</div> <!-- ._post-footer-line-2 -->
</footer> <!-- ._post-footer -->

</div> <!-- ._post-body-area -->

</article> <!-- ._post -->

</b:includable>

Salin semua kode di atas untuk menggantikan kode yang tampak seperti ini di dalam templat:

<b:includable id='post' var='post'> ... </b:includable>

Langkah berikutnya adalah menambahkan CSS khusus. Pilih salah satu saja.

Tata Letak 1: Thumbnail di Sebelah Kiri Judul dan Ringkasan Posting

Post Thumbnail on the Left
Thumbnail di sebelah kiri judul dan ringkasan posting.

Salin kode CSS ini lalu letakkan di atas ]]></b:skin> atau </style>:

/*!
Thumbnail width: 200
Thumbnail height: 200
*/

._post {
background-color:white;
height:200px; /* Thumbnail height */
margin:0 0 10px;
overflow:hidden;
*zoom:1;
}

._post:after {
content:" ";
display:block;
clear:both;
}

._post-title {
font:normal bold 20px/1.2 Arial,Sans-Serif;
margin:0 0 10px;
padding:0;
}

._post-thumbnail-area {
width:200px; /* Thumbnail width */
height:200px; /* Thumbnail height */
background-color:#2D3957;
float:left;
overflow:hidden;
}

._post-thumbnail {
display:block;
width:100%;
height:100%;
max-width:none;
max-height:none;
min-width:0;
min-height:0;
margin:0;
padding:0;
border:none;
outline:none;
position:static;
}

._post-body-area {
padding:20px;
margin-left:200px; /* Thumbnail width */
font-size:11px;
}

._post-footer {
margin:10px 0 0;
padding:10px 0 0;
border-top:1px dotted #ddd;
font:normal bold 8px/1.5 Verdana,Tahoma,Arial,Sans-Serif;
text-transform:uppercase;
color:#999;
}

/* Item + Static Page */
._post-item,
._post-static_page {height:auto}

._post-item ._post-body-area,
._post-static_page ._post-body-area {
margin:0;
padding:20px;
font-size:13px;
}

._post-item ._post-title,
._post-static_page ._post-title {font-size:40px}

Tata Letak 2: Thumbnail di Sebelah Atas Judul dan Ringkasan Posting

Thumbnail Before Post Title and Summary
Thumbnail di sebelah atas judul dan ringkasan posting.

Salin kode CSS ini lalu letakkan di atas ]]></b:skin> atau </style>:

/*!
Thumbnail width: 300
Thumbnail height: 300
*/

._post {
background-color:white;
width:300px; /* Thumbnail width */
margin:0 10px 10px 0;
overflow:hidden;
*zoom:1;
/* float:left; */
}

._post:after {
content:" ";
display:block;
clear:both;
}

._post-title {
font:normal bold 16px/1.2 Arial,Sans-Serif;
margin:0 0 10px;
padding:0;
}

._post-thumbnail-area {
width:300px; /* Thumbnail width */
height:300px; /* Thumbnail height */
background-color:#2D3957;
overflow:hidden;
}

._post-thumbnail {
display:block;
width:100%;
height:100%;
max-width:none;
max-height:none;
min-width:0;
min-height:0;
margin:0;
padding:0;
border:none;
outline:none;
position:static;
}

._post-body-area {
padding:20px;
margin:10px 0 0;
font-size:11px;
}

._post-footer {
margin:10px 0 0;
padding:10px 0 0;
border-top:1px dotted #ddd;
font:normal bold 8px/1.5 Verdana,Tahoma,Arial,Sans-Serif;
text-transform:uppercase;
color:#999;
}

/* Item + Static Page */
._post-item,
._post-static_page {width:auto}

._post-item ._post-body-area,
._post-static_page ._post-body-area {
margin:0;
padding:20px;
font-size:13px;
}

._post-item ._post-title,
._post-static_page ._post-title {font-size:40px}

Sampai di sini sudah selesai. Kemudian, mungkin Anda ingin memperbaiki resolusi gambar yang saat ini masih berada pada resolusi s72-c, sehingga gambar masih terlihat buram. Anda bisa menggunakan JavaScript yang pernah Saya berikan sebelumnya.

Memperbaiki Resolusi Gambar

Salin kode ini:

<b:if cond='data:blog.pageType != &quot;static_page&quot;'>
<b:if cond='data:blog.pageType != &quot;item&quot;'>
<script>
//<![CDATA[
function resizeThumb(parentID, size) {
var parent = document.getElementById(parentID),
image = parent.getElementsByTagName('img');
for (var i = 0; i < image.length; i++) {
image[i].src = image[i].src.replace(/\/s72\-c/, "/s" + size + "-c");
image[i].width = size;
image[i].height = size;
}
} resizeThumb('Blog1', 200);
//]]>
</script>
</b:if>
</b:if>

Kemudian letakkan dengan posisi seperti ini terhadap widget Blog1:

<b:section class='main' id='main' showaddelement='no'>
<b:widget id='Blog1' locked='true' title='Posting Blog' type='Blog'/>
</b:section>
<b:if cond='data:blog.pageType != &quot;static_page&quot;'>
<b:if cond='data:blog.pageType != &quot;item&quot;'>
<script> ... </script>
</b:if>
</b:if>

Kode 200 adalah resolusi gambar baru yang ingin Anda terapkan. Ini tergantung dari ukuran standar masing-masing gambar posting di atas, yang sudah Saya sertakan dalam komentar CSS.

Simpan perubahan.


Struktur Posting yang Tercipta

Berikut ini adalah diagram markup HTML yang akan tercipta secara garis besar. Semoga bisa sedikit membantu untuk memberikan gambaran modifikasi lanjutan:

  • ._post
    • ._post-part._post-thumbnail-area
      • ._post-thumbnail
    • ._post-part._post-body-area
      • ._post-title
      • ._post-body
      • ._post-footer
        • ._post-footer-line._post-footer-line-1
          • ._post-author
          • ._post-timestamp
          • ._post-comment-link
        • ._post-footer-line._post-footer-line-2
          • ._post-labels
._post {}
._post-part {}
._post-thumbnail-area {}
._post-body-area {}
._post-title {}
._post-body {}
._post-footer {}
._post-footer-line {}
._post-footer-line-1 {}
._post-footer-line-2 {}
._post-author {}
._post-timestamp {}
._post-comment-link {}
._post-labels {}

Catatan Tambahan: Setelah ini lebih baik Anda hapus beberapa kode CSS yang memiliki selektor seperti ini. Markup posting sudah berubah secara total, sehingga CSS ini sudah tidak diperlukan lagi:

.post {}
.post-title {}
.post-body {}
.post-footer {}
.post-footer-line {}
.post-footer-line-1 {}
.post-footer-line-2 {}
.post-footer-line-3 {}
.post-header-line {}
.post-header-line-1 {}
.post-header-line-2 {}
.post-header-line-3 {}
.post-author {}
.post-timestamp {}
.post-comment-link {}
.post-labels {}

Jumat, 27 September 2013

Data Asinkron dengan Callback

Fungsi ini bisa digunakan untuk memuat file JavaScript secara asinkron, sekaligus juga memungkinkan pengguna untuk mengeksekusi fungsi sesaat setelah data tersebut berhasil termuat:

function asyncData(url, callback) {
var script = document.createElement('script');
script.src = url;
script.onload = function() {
if (typeof callback == "function") callback();
callback = null;
};
script.onreadystatechange = function() {
if (script.readyState == 4 || script.readyState == "complete") {
if (typeof callback == "function") callback();
callback = null;
}
};
document.getElementsByTagName('head')[0].appendChild(script);
}

Penggunaan

Sebagai contoh, Anda ingin memuat skrip Syntax Highighter secara tidak langsung. Namun pada saat yang bersamaan Anda juga ingin memastikan agar eksekusi plugin terjadi hanya jika skrip tersebut telah termuat:

asyncData("../js/highlight.min.js", function() {
hljs.initHighlighting();
});

Demo

Skrip Asinkron Dasar (Tanpa Fitur Callback)

var script = document.createElement('script');
script.src = "../js/highlight.min.js";
document.getElementsByTagName('head')[0].appendChild(script);

Rabu, 25 September 2013

Membuat Formulir Kontak Google Doc Agar Bisa Mengirimkan Datanya Langsung ke Kotak Pesan Email

Sebenarnya Google Doc bukan merupakan layanan untuk membuat aplikasi-aplikasi semacam ini. Membuat formulir kontak menggunakan Google Doc hanya bisa menampilkan pesan di spreadsheet, dan bukannya langsung menuju kotak masuk pesan seperti yang kita kehendaki. Karena fungsi utama Google Doc pada dasarnya memang bukan untuk membuat formulir kontak semacam itu, melainkan untuk membuat formulir yang terintegrasi dengan dokumen, sehingga ini memungkinkan pengguna untuk bisa mengirimkan data secara online. Dan dari formulir itulah data bisa langsung terkirim dan akan tersimpan ke dalam dokumen berbentuk tabel.

Blogger Xpertise memiliki solusi agar data pesan yang dikirimkan melalui formulir Google Doc bisa langsung terkirim menuju pesan masuk email Anda seperti ini:

Contact results - Google Drive
Data kontak langsung terlihat di pesan masuk.

Di sini Saya akan menjelaskan langkah-langkah pembuatan formulir kontak dimulai dari awal sampai akhir. Ada dua bagian utama yang harus Anda kerjakan di sini, yaitu membuat formulir kontak dan mengelola spreadsheet yang terkait dengan formulir kontak tersebut.

Membuat Formulir Kontak

Di sini Anda akan diajak untuk membuat formulir kontak menggunakan Google Doc.

Pertama-tama buka halaman dasbor Google Drive Anda, lalu buat sebuah formulir:

Create a new form - Google Drive
Membuat formulir baru.

Tentukan judul dan deskripsi formulir, serta buat beberapa kotak pertanyaan yang umum terdapat pada formulir kontak di web. Misalnya: Perihal, Nama, Email, Alamat Web dan Pesan:

Form configuration - Google Drive
Membuat formulir.

Pada bagian Laman Konfirmasi, tuliskan pesan terima kasih yang ingin Anda tampilkan ketika pesan telah berhasil terkirim:

Sent message setup - Google Drive
Menyunting pesan ucapan terima kasih.

Klik tombol Kirim Formulir. Akan muncul kotak dialog baru. Klik tombol Sematkan. Di situ Anda akan diberi kode embed formulir kontak yang bisa Anda pasang pada halaman web Anda. Atau Anda juga bisa melihat hasil jadinya pada halaman formulir khusus dengan menekan tombol Lihat Bentuk Jadi di panel atas, tepat di bawah menu utama.

Klik tombol Pilih Tujuan Tanggapan, lalu cek opsi Spreasheet baru. Berikan judul, misalnya Data Tanggapan:

Create new spreadsheet - Google Drive
Membuat spreadsheet baru.

Klik tombol Buat. Tunggu sampai tombol Lihat Tanggapan muncul. Pada tampilan Google Doc saat ini, tombol Lihat Tanggapan akan muncul untuk menggantikan tombol Pilih Tujuan Tanggapan. Klik tombol tersebut untuk melihat spreadsheet formulir kontak Anda.

Mengelola Data yang Masuk

Setelah spreadsheet tercipta, sekarang adalah saatnya untuk mengelola data yang masuk agar setiap kali data baru terkirim ke dokumen, maka salinan data tersebut bisa langsung masuk ke kotak pesan email.

Pilih menu Alat » Editor skrip:

Script editor menu - Google Drive
Editor skrip.

Anda akan dibawa menuju halaman proyek tak berjudul. Pada formulir bernama Kode.gs tempelkan JavaScript ini:

function sendFormByEmail(e) {

// Tentukan teks subjek/perihal yang nantinya akan muncul pada pesan masuk email setiap kali pesan baru terkirim
var emailSubject = "PESAN BARU!";

// Tentukan alamat email di sini atau beberapa alamat email sekaligus yang dipisahkan dengan tanda koma
var yourEmail = "email@domain.com";

// Masukkan kunci spreadsheet yang terhubung dengan formulir kontak ini
// Bisa ditemukan pada URL ketika Anda melihat spreadsheet tersebut
var docKey = "0Ah0bOy8H_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";

// Jika Anda menginginkan skrip ini mengirim data secara otomatis ke semua editor sheet, set nilainya menjadi `1`
// Jika Anda hanya ingin mengirimkan data ke `yourEmail`, set nilainya menjadi `0`
var useEditors = 1;

// Apakah Anda telah menambahkan kolom yang ternyata tidak Anda gunakan pada formulir?
// Jika ya, set nilai ini ke nomor urut kolom terakhir yang Anda tambahkan pada spreadsheet.
// Sebagai contoh: Kolom `C` ada pada nomor urut ke 3
var extraColumns = 0;

if (useEditors) {
var editors = DocsList.getFileById(docKey).getEditors();
var notify = (editors) ? editors.join(',') : yourEmail;
} else {
var notify = yourEmail;
}

// Variabel `e` memegang semua data di dalam array.
// Loop semua data di dalam array dan sisipkan nilainya ke pesan.
var s = SpreadsheetApp.getActive().getSheetByName("Data1");
if (extraColumns) {
var headers = s.getRange(1, 1, 1, extraColumns).getValues()[0];
} else {
var headers = s.getRange(1, 1, 1, s.getLastColumn()).getValues()[0];
var message = "";
}
for (var i in headers) {
message += headers[i] + ' = ' + e.values[i].toString() + '\n';
}
MailApp.sendEmail(notify, emailSubject, message);
}

Tentukan subjek email, alamat email dan kunci/ID spreadsheet yang tadi Anda buat. ID spreadsheet bisa Anda temukan pada URL spreadsheet terkait:

https://docs.google.com/spreadsheet/ccc?key=0Ah0bOy8H_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX#gid=0

Data1 adalah nama sheet. Pastikan nilainya sama dengan sheet terkait:

Sheet rename - Goole Drive
Memberi nama sheet.

Sekarang pilih menu Sumber Daya » Pemicu proyek saat ini. Akan muncul kata “No triggers set up. Click to add one now”. Klik pada tautan tersebut untuk menambahkan trigger. Pada kolom Events, pastikan opsi yang terpilih adalah From spreadsheet dan On form submit.

Script trigger authorization - Google Drive
Pemicu proyek.…

Klik opsi notification. Pada opsi ke dua di kotak dialog Execution failure notifications, pilih immediately.

Klik OK.

Akan muncul pesan authorisasi skrip. Klik tombol Terima/Authorize. Ini akan menyetujui authorisasi untuk trigger yang Anda buat.

Anda akan dibawa kembali menuju layar editor. Klik Simpan/Save. Sekarang Anda sudah bisa mencoba mengirimkan pesan melalui formulir kontak buatan Anda. Pembuatan formulir kontak ini sudah selesai.

Setelah ini mungkin Anda akan menerima pesan error ke kotak pesan email. Beruntung jika tidak. Selebihnya hanyalah mengenai pemecahan masalah error yang terjadi. Saya pribadi belum pernah mengalami masalah-masalah yang rumit. Jadi Saya masih belum bisa membuat daftar kemungkinan error yang terjadi di sini. Berikut ini adalah beberapa hal yang perlu diperhatikan menurut Blogger Xpertise:

  1. Pastikan nama sheet tidak mengandung karakter spasi. Ini mungkin bisa menimbulkan errorSheet Saya mengandung karakter spasi, tetapi Saya tidak mengalami masalah itu.
  2. Jangan sampai ada kolom yang kosong yang Anda tambahkan ke dalam spreadsheet kontak. Ini akan menciptakan masalah pada perataan data yang masuk serta membuat skrip gagal bekerja ⇐ Saya mengalami masalah ini sebelumnya gara-gara menambahkan dan memindahkan kolom baru, serta karena menambahkan field pertanyaan baru pada formulir kontak yang Saya buat. Jadi pastikan formulir yang Anda buat sudah benar-benar menetap.
  3. Nama kolom terkadang juga dapat menimbulkan masalah. Pastikan Anda membuat nama kolom/pertanyaan formulir dengan karakter teks yang standar.

Bacaan Lebih Lanjut

Kamis, 19 September 2013

XHTML Blogger, Widget Label

Sebuah widget Blogger bertipe Label dapat dinyatakan dalam kerangka seperti ini:

<b:widget id='Label1' locked='false' title='Category List' type='Label'>
<b:includable id='main'>
<b:if cond='data:title'>
<h2><data:title/></h2>
</b:if>
<div expr:class='&quot;widget-content &quot; + data:display + &quot;-label-widget-content&quot;'>
<b:if cond='data:display == &quot;list&quot;'>
<ul>
<b:loop values='data:labels' var='label'>
<li>
<b:if cond='data:blog.url == data:label.url'>
<span expr:dir='data:blog.languageDirection'><data:label.name/></span>
<b:else/>
<a expr:dir='data:blog.languageDirection' expr:href='data:label.url'><data:label.name/></a>
</b:if>
<b:if cond='data:showFreqNumbers'>
<span dir='ltr'>(<data:label.count/>)</span>
</b:if>
</li>
</b:loop>
</ul>
<b:else/>
<b:loop values='data:labels' var='label'>
<span expr:class='&quot;label-size label-size-&quot; + data:label.cssSize'>
<b:if cond='data:blog.url == data:label.url'>
<span expr:dir='data:blog.languageDirection'><data:label.name/></span>
<b:else/>
<a expr:dir='data:blog.languageDirection' expr:href='data:label.url'><data:label.name/></a>
</b:if>
<b:if cond='data:showFreqNumbers'>
<span class='label-count' dir='ltr'>(<data:label.count/>)</span>
</b:if>
</span>
</b:loop>
</b:if>
<b:include name='quickedit'/>
</div>
</b:includable>
</b:widget>

Saat Anda mencoba mengedit atau baru pertama kali menambahkan widget label di blog Anda, maka Anda akan menemukan dua opsi utama untuk menentukan tampilan widget yaitu model widget bertipe list dan cloud. Kode di atas sebenarnya juga sudah menunjukkan markup XML kedua model tersebut. Saya akan pisahkan bagian-bagiannya agar bisa lebih mudah dimengerti:

Judul Widget

Bagian ini menunjukkan markup judul widget. Kondisional yang mengelilinginya berfungsi untuk membatasi agar elemen <h2> pada widget ini hanya muncul jika formulir judul tidak kosong:

<b:if cond='data:title'>
<h2><data:title/></h2>
</b:if>

Kondisional Tipe Widget

Bagian ini menunjukkan kondisional yang berfungsi untuk memisahkan tampilan model list dan cloud:

<b:if cond='data:display == &quot;list&quot;'>
<!-- Markup widget bertipe `list` -->
<b:else/>
<!-- Markup widget bertipe selain `list` -->
</b:if>

Markup Widget Bertipe List

<ul>
<b:loop values='data:labels' var='label'>
<li>
<b:if cond='data:blog.url == data:label.url'>
<span expr:dir='data:blog.languageDirection'><data:label.name/></span>
<b:else/>
<a expr:dir='data:blog.languageDirection' expr:href='data:label.url'><data:label.name/></a>
</b:if>
<b:if cond='data:showFreqNumbers'>
<span dir='ltr'>(<data:label.count/>)</span>
</b:if>
</li>
</b:loop>
</ul>

Markup Widget Bertipe Cloud

<b:loop values='data:labels' var='label'>
<span expr:class='&quot;label-size label-size-&quot; + data:label.cssSize'>
<b:if cond='data:blog.url == data:label.url'>
<span expr:dir='data:blog.languageDirection'><data:label.name/></span>
<b:else/>
<a expr:dir='data:blog.languageDirection' expr:href='data:label.url'><data:label.name/></a>
</b:if>
<b:if cond='data:showFreqNumbers'>
<span class='label-count' dir='ltr'>(<data:label.count/>)</span>
</b:if>
</span>
</b:loop>
DataKeteranganTampilan/Contoh Tampilan
data:displayElemen ini akan menampilkan tipe tampilan widget. Berguna untuk membuat kelas spesifik, terutama pada elemen tubuh widget.list, cloud
data:blog.url[?]
data:label.urlElemen ini akan menampilkan URL setiap item label.http://nama_blog.blogspot.com/search/label/-/Nama Label
data:label.nameElemen ini akan menampilkan nama label.Nama Label
data:blog.languageDirection[?]
data:showFreqNumbersKondisional untuk menyatakan apakah jumlah posting pada widget akan ditampilkan atau tidak.true, false
data:label.countElemen ini akan menampilkan jumlah posting pada label terkait.20
data:label.cssSizeElemen ini akan menampilkan angka tingkatan berdasarkan perbandingan banyaknya jumlah posting antara label yang satu dengan label yang lainnya. Biasa digunakan sebagai akhiran nama kelas label-size-* pada item label bertipe cloud untuk mengatur besar ukuran masing-masing item melalui CSS.3

Ikon Pengeditan Cepat

<b:include name='quickedit'/>

Contoh Markup Widget Label Hasil Modifikasi

Berikut ini adalah contoh markup XML yang akan mengubah tampilan widget label menjadi berbentuk tabel:

<b:widget id='Label1' locked='false' title='Category List' type='Label'>
<b:includable id='main'>
<b:if cond='data:title'>
<h2><data:title/></h2>
</b:if>
<div class='widget-content'>
<table border='1'>
<thead>
<tr><th>Label</th><th>Total Posting</th></tr>
</thead>
<tbody>
<b:loop values='data:labels' var='label'>
<!--
Kondisional `data:blog.url == data:label.url` tidak efektif dan tidak tepat sasaran!
Gunakan cara Saya yang ini untuk menciptakan kondisional halaman label yang stabil.
-->
<b:if cond='data:blog.searchLabel == data:label.name'>
<tr><td><strong><data:label.name/></strong></td><td><data:label.count/></td></tr>
<b:else/>
<tr><td><a expr:href='data:label.url'><data:label.name/></a></td><td><data:label.count/></td></tr>
</b:if>
</b:loop>
</tbody>
</table>
<b:include name='quickedit'/>
</div>
</b:includable>
</b:widget>

Kamis, 15 Agustus 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 pasti 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>

Sabtu, 03 Agustus 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="http://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>

Jumat, 19 Juli 2013

Daftar Snippet untuk Implementasi DOM Range API

Mendapatkan Teks yang Terseleksi

Paling dasar. Fungsi ini digunakan untuk mengambil teks yang terseleksi. Hanya teks. Tag HTML tidak akan direpresentasikan sebagai node apa adanya. Untuk mengubah node terseleksi menjadi HTML, gunakan fungsi ini:

function getSelectionText() {
var sel = "";
if (window.getSelection) {
sel = window.getSelection();
} else if (document.getSelection) {
sel = document.getSelection();
} else if (document.selection) { // IE
sel = document.selection.createRange().text;
}
return sel;
}

Contoh Penggunaan

document.onmouseup = function() {
alert(getSelectionText());
};

Seleksi Teks Otomatis

function autoSelect(elem) {
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
range = document.createRange();
range.selectNodeContents(elem);
sel.removeAllRanges();
sel.addRange(range);
} else if (document.selection) { // IE
sel = document.selection.createRange().text;
range = document.body.createTextRange();
range.moveToElementText(elem);
range.select();
}
}

Konversi Node Terseleksi ke HTML

Fungsinya adalah untuk membawa bagian-bagian yang terseleksi menjadi HTML dalam bentuk string, sehingga hasil yang didapatkan bisa ditempelkan ke area tertentu sebagai duplikat yang sama dengan bagian yang terseleksi:

// http://stackoverflow.com/a/4652824
function getSelectionHTML() {
var html = "";
if (typeof window.getSelection != 'undefined') {
var sel = window.getSelection();
if (sel.rangeCount) {
var container = document.createElement('div'); // Just a placeholder
for (var i = 0, len = sel.rangeCount; i < len; ++i) {
container.appendChild(sel.getRangeAt(i).cloneContents());
}
html = container.innerHTML;
}
} else if (typeof document.selection != 'undefined') {
if (document.selection.type == 'Text') {
html = document.selection.createRange().htmlText;
}
}
return html;
}

Contoh Penggunaan

Sisipkan bagian yang terseleksi ke dalam area #result-container sebagai HTML:

document.getElementById('action-btn').onclick = function() {
document.getElementById('result-container').innerHTML = getSelectionHTML();
};

Manipulasi Posisi dan Jarak Seleksi

Fungsi ini digunakan untuk membuat jarak seleksi pada posisi yang telah ditentukan. Cuma berlaku untuk elemen formulir. Menentukan nilai yang sama pada parameter selectionStart dan selectionEnd memungkinkan fungsi ini untuk digunakan sebagai pengatur letak caret teks:

// http://stackoverflow.com/a/499158
function setSelectionRange(input, selectionStart, selectionEnd) {
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
} else if (input.createTextRange) {
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', selectionEnd);
range.moveStart('character', selectionStart);
range.select();
}
}

Contoh Penggunaan

document.getElementById('action-btn').onclick = function() {
setSelectionRange(document.getElementById('target-textarea'), 10, 100);
};

Memposisikan Caret ke Bagian Akhir Area Teks

// http://stackoverflow.com/a/4716021
function moveCaretToEnd(el) {
if (typeof el.selectionStart == 'number') {
el.selectionStart = el.selectionEnd = el.value.length;
} else if (typeof el.createTextRange != 'undefined') {
el.focus();
var range = el.createTextRange();
range.collapse(false);
range.select();
}
}

Contoh Penggunaan

<textarea onfocus="moveCaretToEnd(this);"> ... </textarea>

Menyimpan dan Mengembalikan Teks Terseleksi

Fungsi pertama digunakan untuk mendapatkan teks terseleksi agar bisa Anda simpan ke dalam variabel. Fungsi ke dua digunakan untuk mengembalikan node terseleksi yang telah Anda simpan tadi:

// http://stackoverflow.com/a/2925633

var savedSelection = null; // Variable to save the selected node

function saveSelection() {
if (window.getSelection) {
var sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
return sel.getRangeAt(0);
}
} else if (document.selection && document.selection.createRange) {
return document.selection.createRange();
}
return null;
}

function restoreSelection(range) {
if (range) {
if (window.getSelection) {
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
} else if (document.selection && range.select) {
range.select();
}
}
}

Penggunaan

document.getElementById('save-selection-btn').onclick = function() {
savedSelection = saveSelection();
};

document.getElementById('restore-selection-btn').onclick = function() {
restoreSelection(savedSelection);
};

Mendapatkan Jarak Awal dan Akhir Teks Terseleksi pada Area Teks

function getInputSelection(el) { // http://stackoverflow.com/a/3053640

var start = 0,
end = 0,
normalizedValue,
range,
textInputRange,
len,
endRange;

if (typeof el.selectionStart == 'number' && typeof el.selectionEnd == 'number') {

start = el.selectionStart;
end = el.selectionEnd;

} else {

range = document.selection.createRange();

if (range && range.parentElement() == el) {

len = el.value.length;
normalizedValue = el.value.replace(/\r\n/g, "\n");

// Create a working text range that lives only in the input
textInputRange = el.createTextRange();
textInputRange.moveToBookmark(range.getBookmark());

// Check if the start and end of the selection are at the very end
// of the input, since `moveStart`/`moveEnd` doesn't return what we want
// in those cases
endRange = el.createTextRange();
endRange.collapse(false);

if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
start = end = len;
} else {
start = -textInputRange.moveStart("character", -len);
start += normalizedValue.slice(0, start).split("\n").length - 1;
if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
end = len;
} else {
end = -textInputRange.moveEnd("character", -len);
end += normalizedValue.slice(0, end).split("\n").length - 1;
}
}

}

}

return {
start: start,
end: end
};

}

Penggunaan

document.getElementById('test-btn').onmousedown = function() {
var position = getInputSelection(document.getElementById('test-textarea'));
alert('Start: ' + position.start + ', End: ' + position.end);
};

Menyisipkan Sesuatu Setelah Caret

1. <div contenteditable>

Fungsi yang bisa bekerja pada elemen HTML dengan atribut contenteditable:

// http://stackoverflow.com/a/6691294
function pasteHtmlAtCaret(html) {
var sel, range, el, frag, node, lastNode;
if (window.getSelection) {
// IE9 and non-IE
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
// `Range.createContextualFragment()` would be useful here but is
// non-standard and not supported in all browsers (IE9, for one)
el = document.createElement('div');
el.innerHTML = html;
frag = document.createDocumentFragment();
while ((node = el.firstChild)) {
lastNode = frag.appendChild(node);
}
range.insertNode(frag);
// Preserve the selection
if (lastNode) {
range = range.cloneRange();
range.setStartAfter(lastNode);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
}
} else if (document.selection && document.selection.type != 'Control') {
// IE < 9
document.selection.createRange().pasteHTML(html);
}
}

Contoh Penggunaan

Sisipkan emoticon setelah kursor caret:

document.getElementById('insert-btn').onclick = function() {
pasteHtmlAtCaret('<img src="emoticon.gif" width="16" height="16">');
};

2. <textarea>

Fungsi yang bisa bekerja pada elemen formulir teks seperti <textarea>:

// http://stackoverflow.com/a/11070952
function insertStringAtCaret(str, elem) {
var value = elem.value,
before = value.substring(0, elem.selectionStart),
after = value.substring(elem.selectionEnd, value.length),
pos = before.length + str.length;
elem.value = before + str + after;
setSelectionRange(elem, pos, pos);
}

function setSelectionRange(input, selectionStart, selectionEnd) {

#code-snippet:manipulasi-posisi-dan-jarak-seleksi

}

Contoh Penggunaan

<button onmousedown="insertStringAtCaret(' :D ', document.getElementById('target-textarea'));">:D</button>

Menyisipkan Sesuatu Sebelum dan Setelah Teks Terseleksi

Pada umumnya digunakan untuk menyisipkan tag HTML sebelum teks terseleksi dan setelah teks terseleksi. Fungsi ini Saya buat berdasarkan fungsi di atas:

// Mendukung untuk semua peramban mayor dan IE8+
function wrapText(str1, str2, elem) {
var value = elem.value,
before = value.substring(0, elem.selectionStart),
selected = value.substring(elem.selectionStart, elem.selectionEnd),
after = value.substring(elem.selectionEnd, value.length),
endpos = before.length + str1.length + selected.length + str2.length;
elem.value = before + str1 + selected + str2 + after;
setSelectionRange(elem, before.length, endpos);
}

function setSelectionRange(input, selectionStart, selectionEnd) {

#code-snippet:manipulasi-posisi-dan-jarak-seleksi

}

Contoh Penggunaan

<button onclick="wrapText('<b>', '</b>', document.getElementById('target-textarea'));">Bold</button>

Mendapatkan Elemen Induk dari Teks yang Terseleksi

// http://stackoverflow.com/a/1336922
// The following will return the container element of the start or end boundary of the current selection
// `getSelectionBoundaryElement()` will select the last container element of the current multiple container selection.
// `getSelectionBoundaryElement(true)` will select the first container element of the current multiple container selection.
function getSelectionBoundaryElement(isStart) {
var range, sel, container;
if (document.selection) {
range = document.selection.createRange();
range.collapse(isStart);
return range.parentElement();
} else {
sel = window.getSelection();
if (sel.getRangeAt) {
if (sel.rangeCount > 0) {
range = sel.getRangeAt(0);
}
} else {
// Old WebKit
range = document.createRange();
range.setStart(sel.anchorNode, sel.anchorOffset);
range.setEnd(sel.focusNode, sel.focusOffset);
// Handle the case when the selection was selected backwards (from the end to the start in the document)
if (range.collapsed !== sel.isCollapsed) {
range.setStart(sel.focusNode, sel.focusOffset);
range.setEnd(sel.anchorNode, sel.anchorOffset);
}
}
if (range) {
container = range[isStart ? "startContainer" : "endContainer"];
// Check if the container is a text node and return its parent if so
return container.nodeType === 3 ? container.parentNode : container;
}
}
}

Contoh Penggunaan

function getSelectionText() {

#code-snippet:mendapatkan-teks-yang-terseleksi

}

document.onmouseup = function() {
if (getSelectionText().toString() !== "") {
var parentElem = getSelectionBoundaryElement();
parentElem.style.backgroundColor = "#ff0";
}
};

Selasa, 16 Juli 2013

Lisensi, Atribusi dan Hak Cipta

Sebuah hasil karya yang dipublikasikan tanpa lisensi tidak memiliki artian bahwa hasil karya tersebut merupakan karya yang bebas atau telah dibuang. Selalu ada beberapa hal yang akan mengikat Anda sebagai pihak pengguna, tidak peduli apakah pihak pencipta telah memberitahukannya kepada Anda atau tidak.

Dalam sebuah produk karya ciptaan, setidaknya terdapat minimal 5 persyaratan yang akan mengikat Anda sebagai seorang calon pemakai hasil karya secara tidak langsung, meski sang pembuat produk tersebut tidak memberitahukannya sejak awal. Beberapa persyaratan yang Saya maksud diantaranya adalah:

  1. Apakah boleh dipakai?
  2. Apakah boleh dimodifikasi?
  3. Apakah boleh disebarluaskan?
  4. Apakah boleh menghapus atribusi pembuat hasil karya?
  5. Apakah boleh dijual?

1. Apakah boleh dipakai?

Kalau tidak boleh dipakai ya jangan dipakai!!! Kamu itu bagaimana sih?! Itu kan bukan barang-barang kepunyaanmu.

2. Apakah boleh dimodifikasi?

Kemungkinanya boleh. Jika boleh, maka umumnya syarat legal yang harus Anda penuhi sebagai seseorang yang akan memodifikasi hasil karya adalah dengan cara tetap mencantumkan nama si pembuat asli hasil karya tersebut, untuk kemudian bisa diikuti dengan nama Anda di bawahnya. Itu adalah cara terbaik dan paling miskin yang bisa Anda lakukan untuk menghargai karya ciptaan orang lain, terutama jika Anda tidak memiliki biaya untuk membeli hak cipta dari hasil karya yang sedang coba Anda pakai. Misalnya seperti ini:

/*
Created by Taufik Nurrohman (http://www.my_site.com)
Modified by Lorem Ipsum (http://www.your_site.com)
*/

Pastikan nama dan alamat sumber tertulis secara jelas dan jangan sekali-kali berusaha untuk mengaburkan sumber aslinya, karena itu bisa mengakibatkan kebingungan bagi para pengguna lain yang mungkin tidak sengaja memakai hasil karya salinan dengan lisensi atribusi yang sudah tidak benar. Jika itu terjadi, setiap orang bisa saja mencoba untuk mengambil kepemilikan dari hasil karya tersebut. Jika kebetulan orang tersebut adalah orang penting dan merasa bahwa hasil karya tersebut adalah sesuatu yang sangat berharga baginya, kemungkinan orang tersebut akan berubah gila dan akan menuntut secara hukum siapa saja yang mengaku sebagai pembuat asli hasil karya tersebut, padahal dia sendiri sebenarnya juga bukan pembuat asli hasil karya tersebut. Hanya karena dia punya banyak uang dan memiliki pengacara, maka dia bisa memenangkan hak cipta dari sebuah hasil karya dengan cara yang tidak benar. Jika itu sampai terjadi, sang pembuat hasil karya yang asli bisa terjerat masalah hukum yang seharusnya tidak ditimpakan kepadanya hanya karena perbuatanmu.

3. Apakah boleh disebarluaskan?

Jika ternyata boleh, pastikan semua data masih dalam keadaan utuh sebagaimana mestinya. Jangan ada bagian manapun yang Anda modifikasi.

Terkadang Saya merasa heran dengan para distributor template atau tema blog yang suka mencantumkan tautan mereka di footer hasil karya orang lain seperti ini:

Attribution of a creation.
Menyisipkan atribusi di sekitar atribusi orang lain tanpa izin.

Sebagai seorang distributor, sebenarnya wajar jika Anda sempat terpikir (sampai kemudian melakukannya) untuk “numpang” menambahkan tautan Anda di sekitar atribusi mereka agar bisa memperoleh trafik secara gratis dari para pemakai. Namanya juga kesempatan. Tapi apakah tidak bisa jika Anda mencantumkan tautan Anda secara terpisah saja, misalnya di dalam sebuah file teks dengan isi seperti ini. Menurut Saya cara ini lebih sopan:

Readme file.
File readme untuk mencantumkan alamat sumber distributor.

Berikut ini adalah salah satu contoh file lisensi yang Saya dapatkan dari dalam sebuah paket font berlisensi gratis:

  *        *
hello there font lover you've downloaded a
* *
SOLAR*SISTER font

from www.hellostranger.com/solarsister/

* * *
* * * *
* *
there are only a few rules:
* *
The fonts are all free ware, which means you can do pretty much
whatever the hell you want with them, except claim them as your own,
of course. If you use them though, I'd like to see where. Drop me a line:

solarsister@hellostranger.com.
* *
However, if you want to redistribute any of the typefaces contained
herein, please ask first. Of course, this read-me file must be attatched.

* * *
* * * *
*

send me a postcard with your e-mail address and I'll send you a special font
* * * *
PO Box 616
Mt Lawley WA 6929
Australia
* * * *
* * *
* * * *
*

4. Apakah boleh menghapus atribusi pembuat hasil karya?

Ini adalah pertanyaan yang biasa dilontarkan orang-orang gengsi yang ingin tampil mengesankan tapi dengan cara memakai barang-barang hasil ciptaan orang lain. Kebanyakan pembuat hasil karya terbuka/gratis umumnya memperbolehkan Anda untuk menghapus/tidak mencantumkan atribusi mereka selama Anda tidak mengganti atribusi mereka dengan atribusi-atribusi palsu (biasanya dengan cara mengganti nama mereka dengan nama Anda untuk mengklaim bahwa karya tersebut adalah buatan Anda). Jika tidak boleh dihapus, seharusnya mereka akan memberikan peringatan bahwa hasil karya mereka boleh dipakai dengan syarat tetap mencantumkan atribusi mereka. Entah dengan cara mencantumkan tautan sumber pada footer atau bagian-bagian akhir artikel Anda dimana hasil karya orang tersebut terdapat di dalamnya, atau sekedar menuliskannya di dalam komentar-komentar file HTML, CSS dan JavaScript jika karya tersebut merupakan kode/teks. Tapi Saya sarankan tetap pertahankan saja atribusi mereka untuk menghargai apa yang berhasil mereka ciptakan.

5. Apakah boleh dijual?

Untuk hasil karya bebas yang biasa tersebar di internet, umumnya selalu terdapat semacam file teks berjudul readme di dalam paket unduhan yang bisa Anda baca untuk mengetahui syarat-syarat apa saja yang harus dipenuhi oleh Anda sebagai calon pemakai hasil karya tersebut. Untuk karya-karya dengan lisensi gratis biasanya cukup dengan mencantumkan atribusi sang pembuat hasil karya pada tempat-tempat yang mereka inginkan. Setelah itu Anda sudah bisa memakainya secara legal. Namun beberapa kegiatan lain seperti penghapusan atribusi atau penjualan hasil karya biasanya harus mengikuti langkah-langkah yang lebih rumit. Akan terdapat beberapa syarat yang akan mengarahkan Anda kepada aktivitas pemberian sejumlah uang, karena Anda sedang mencoba untuk mengomersialisasikan dan/atau menghilangkan jejak dari sesuatu yang bukan merupakan milik Anda. Syarat yang paling sederhana misalnya, berupa pemberian sejumlah uang sebagai donasi atau melakukan perjanjian royalti. Seperti itulah. Ini contohnya:

anke-art fonts = Donation Ware!

Hi! This is another annoying readme-file.
But if you like my font, I'm asking you to
put a link to my page www.anke-art.de on YOUR page.
On my homepage you can find a little logo in the
upper left corner which you can copy as a link.

If you don't want to link to my page,
but still want to use my font(s) nevertheless,
please send any amount in US-Dollars (which you think the font is worth) to:

Anke Arnold
Fabrikstrasse 62
73240 Wendlingen
Germany

Thank you sooo much!

Mengenai Penggantian Atribusi Hasil Karya

Pembolehan penggantian atribusi Saya rasa tidak akan pernah ada di dalam persyaratan seorang pembuat hasil karya sampai kapanpun, karena itu sudah masuk ke dalam wilayah kejahatan plagiarisme. Yaitu ketika Anda mencoba untuk menipu banyak orang dengan cara mengambil hasil karya orang lain yang menurut Anda bisa membuat orang-orang di sekitar Anda terkesan, kemudian mengklaim bahwa hasil karya tersebut merupakan hasil karya ciptaan Anda. Itu dilakukan tidak lain untuk memperoleh pencitraan yang baik dalam diri Anda atau untuk mencapai tujuan-tujuan lain yang berbau komersial sampai ke arah usaha-usaha untuk menjatuhkan pihak lain yang sedang Anda benci saat itu.

Memang, mungkin sang pembuat hasil karya memperbolehkan Anda untuk memodifikasi hasil karya mereka, namun bukan berarti Anda diperbolehkan untuk mengganti keterangan pembuat hasil karya tersebut secara keseluruhan dengan nama Anda. Itu ilegal. Bahkan Saya bisa mengatakan bahwa menghapus atribusi masih termasuk dalam kejahatan (kejahatan, dalam artian bahwa si pembuat hasil karya asli tidak memperbolehkan Anda untuk menghapus atribusi mereka) yang bisa dimaafkan, karena Saya sadar bahwa tidak semua orang bisa merasa nyaman jika harus selalu hidup dibawah bayang-bayang nama orang lain melalui barang-barang yang sedang mereka pakai.

Tidak ada yang salah dengan sesuatu yang gratis. Hanya saja, untuk mendapatkan sesuatu secara gratis pasti akan ada beberapa syarat yang harus Anda penuhi. Jika Anda tidak mau mematuhi persyaratan yang mereka berikan, ya Anda jangan sekali-kali memakai hasil karya mereka. Belajarlah untuk membuatnya sendiri.

Sebagai informasi, mempertahankan atribusi adalah satu-satunya cara yang bisa Anda lakukan untuk menghargai para kreator yang telah rela membagikan hasil-hasil karya mereka secara gratis. Karena mereka tidak mendapatkan keuntungan apa-apa dari hasil karya yang mereka buat.

Hubungi Mereka

Meski kemudian pada akhirnya Anda diperbolehkan untuk melanggar beberapa persyaratan yang mereka berikan, tentunya akan terdapat beberapa “ritual” ganti rugi yang harus Anda korbankan sebagai pihak pengguna yang reseh dan banyak maunya. Akan lebih baik jika Anda menghubungi si pembuat hasil karya tersebut secara langsung dan menanyakan mengenai pembolehan Anda sebagai seorang pengguna untuk melakukan sesuatu terhadap hasil karya mereka jika Anda merasa tidak cukup jelas dengan lisensinya. Misalnya dengan cara bertanya seperti ini:

Saya ingin menggunakan foto hasil karya Anda pada halaman ini (merujuk ke sebuah URL atau tempat yang terkait dengan hasil karya yang dimaksud). Jika boleh, Saya akan mancantumkan nama dan alamat web Anda pada halaman kredit web Saya. Jika itu belum cukup, apa saja yang harus Saya lakukan agar Saya diperbolehkan untuk menggunakan hasil karya Anda?

Walau bagaimanapun juga, sebagai seorang pengguna/pemakai Anda harus siap sejak awal untuk memenuhi apa yang diinginkan oleh si pembuat hasil karya jika hendak menggunakan hasil karya tersebut. Bagaimanapun, itu semua bukan barang-barang kepunyaan Anda, jadi Anda harus meminta izin terlebih dahulu untuk memperoleh kesepakatan.

Lisensi untuk Kalangan Amatir

Jika Anda merupakan pihak pencipta, dan Anda ingin melindungi hasil karya Anda yang terbilang lemah di mata hukum karena Anda merupakan seorang amatir yang membuat sesuatu hanya untuk memenuhi hobi/kepuasan diri sendiri, dan tidak melakukannya secara profesional untuk mendapatkan penghasilan, lisensi CC BY-NC-SA sangat cocok dalam kasus ini. Lisensi ini mencakup:

  • Atribusi: Artinya pengguna wajib meninggalkan atribusi dari kreator asli tersebut pada karya yang sedang mereka gunakan.
  • Nonkomersial: Artinya pengguna tidak boleh menjual hasil karya Anda/tidak boleh menggunakan hasil karya Anda untuk hal-hal yang komersial.
  • Berbagi Serupa: Artinya pengguna boleh menyebarluaskan hasil karya Anda, namun dengan syarat bahwa lisensi baru yang diterapkan harus mengikuti lisensi asli dari karya sebelumnya. Biasanya ini terkait pada masalah modifikasi hasil karya.

Memodifikasi hasil-hasil karya yang berada di bawah lisensi ini diperbolehkan, karena tidak terdapat kode ND yang berarti No Derivative Works, atau tidak diperbolehkan untuk menciptakan karya turunan dari karya Anda, atau tidak boleh dimodifikasi. Untuk kalangan amatir, kode SA sangat penting diterapkan pada hasil karya mereka agar mereka bisa memastikan apakah karya-karya yang telah dimodifikasi akan para modifikator publikasikan di bawah lisensi yang sama atau tidak. Karena Saya yakin, Anda tidak akan merasa senang jika suatu saat Anda mendapati barang-barang hasil karya orang lain dijual secara bebas, yang ternyata barang tersebut merupakan hasil karya turunan dari karya Anda. Anda akan rugi karena itu.