Tìm hiểu và cải tiến Auto Readmore

Mọi người dùng Blogger chắc đều biết đến tiện ích Auto Readmore nổi tiếng của AnhVo. Ở bài viết này, tôi sẽ cùng các bạn tìm hiểu qua cách code của tiện ích này, đồng thời đề xuất 1 cách nâng cấp để code được tốt hơn.

Trước hết, chúng ta xem qua nội dung của script này:

function removeHtmlTag(strx, chop) {
 if (strx.indexOf("<") != -1) {
  var s = strx.split("<");
  for (var i = 0; i < s.length; i++) {
   if (s[i].indexOf(">") != -1) {
    s[i] = s[i].substring(s[i].indexOf(">") + 1, s[i].length);
   }
  }
  strx = s.join("");
 }
 chop = (chop < strx.length - 1) ? chop : strx.length - 2;
 while (strx.charAt(chop - 1) != ' ' && strx.indexOf(' ', chop) != -1) chop++;
 strx = strx.substring(0, chop - 1);
 return strx + '...';
}

function createSummaryAndThumb(pID) {
 var div = document.getElementById(pID);
 var imgtag = "";
 var img = div.getElementsByTagName("img");
 var summ = summary_noimg;
 if (img.length >= 1) {
  if (thumbnail_mode == "float") {
   imgtag = '<span style="float:left; padding:0px 10px 5px 0px;"><img src="' + img[0].src + '" width="' + img_thumb_width + 'px" height="' + img_thumb_height + 'px"/></span>';
   summ = summary_img;
  } else {
   imgtag = '<div style="padding:5px" align="center"><img style="max-width:' + img_thumb_width + 'px; max-height:' + img_thumb_height + 'px;" src="' + img[0].src + '" /></div>';
   summ = summary_img;
  }
 }
 var summary = imgtag + '<div>' + removeHtmlTag(div.innerHTML, summ) + '</div>';
 div.innerHTML = summary;
}

Đoạn script này gồm 2 hàm:
- hàm đầu tiên removeHtmlTag dùng để loại bỏ các thẻ HTML và lấy ra chop kí tự đầu tiên của 1 xâu
- hàm thứ 2 createSummaryAndThumb có nhiệm vụ lấy nội dung của bài viết (nơi có ID là pID), lấy ra hình ảnh đầu tiên nằm trong phần nội dung đó, sau đó sử dụng hàm removeHtmlTag để sinh ra đoạn văn bản cần rút gọn

Ứng với mỗi hàm này, chúng ta sẽ đưa ra các cải tiến tương ứng.

1.Cải tiến đầu tiên


Trước tiên, xét hàm removeHtmlTag, ta có thể đưa ra 1 chút nhận xét như sau:

1.1. Về việc loại bỏ các thẻ HTML


Công việc này được thực hiện lần lượt qua các bước sau:
- tìm các kí tự <
- chia xâu văn bản ban đầu thành các mảng dựa vào kí tự đó
- trong mỗi phần được chia, tìm kí tự > tương ứng và xóa phần giữa chúng đi
- cuối cùng là nối tất cả các phần lại với nhau

Có vẻ khá rắc rối! Nhất là ở việc tìm các kí tự < và >. Chúng ta biết rằng Javascript có 1 công cụ cực mạnh để tìm kiếm và thay thế các đoạn văn bản theo mẫu, đó là biểu thức tiêu chuẩn (regular expression). Vì thế, chúng ta sẽ ứng dụng nó thay cho việc dùng các hàm tìm kiếm như trên.

Toàn bộ công việc tìm kiếm, thay thế (loại bỏ) các thẻ HTML đối với biểu thức tiêu chuẩn chỉ cần thực hiện 1 câu lệnh đơn giản như sau:

s = s.replace(/<.*?>/ig, '');

trong đó s là xâu ban đầu. Tôi gán luôn giá trị trả về cho s để khỏi mất công khai báo thêm biến.

1.2. Lấy ra n kí tự đầu tiên


Phần này AnhVo xử lí khá khéo! Khéo ở chỗ đã tính đến việc kí tự cuối cùng bị lấy nằm giữa từ, hơn nữa, có thể là kí tự unicode của tiếng Việt ("chăt" vào giữa chúng có thể gây ra tình trạng kí tự ô vuông). Do đó AnhVo đã dùng 1 mẹo: tìm kiếm kí tự trắng (space) liền sau kí tự cuối cùng và lấy đến đó.

Chỗ này tôi chỉ để xuất 1 cách làm khác thôi, là thay vì cắt theo kí tự thì ta cắt theo từ, như thế xử lí tiện hơn vì không phải lo cắt vào giữa, cũng không phải lo việc tìm kiếm kí tự trắng.

Nếu cắt theo từ, thì có thể dùng tiếp tục biểu thức tiêu chuẩn. Toàn bộ công việc cũng chỉ đơn giản như sau:

s = s.split(/\s+/);
s = s.slice(0,n-1);
s = s.join(' ');

Ở đây có 3 hàm, chúng lần lượt làm các công việc sau:
- chia xâu s ban đầu thành nhiều phần, lấy 1 hoặc nhiều kí tự trắng (bao gồm cả xuống dòng) làm kí tự phân cách
- lấy ra n phần đầu tiên
- nối n phần đó lại với nhau

Kết hợp với phần trước, hàm của chúng ta có cả thảy 4 dòng code:

function stripTags(s, n) {
 s = s.replace(/<.*?>/ig, '');
 s = s.split(/\s+/);
 s = s.slice(0, n - 1);
 s = s.join(' ');
}

Javascript còn có 1 tính năng khá hay là xâu chuỗi các hàm của 1 đối tượng (chain), nhờ đó, toàn bộ hàm đầu tiên của chúng ta có thể viết ngắn gọn lại trong 1 dòng như sau:

function stripTags(s, n) {
 return s.replace(/<.*?>/ig, '').split(/\s+/).slice(0, n - 1).join(' ');
}

Lưu ý 1 chút là trong hàm này, tôi có đổi tên hàm, tên các biến cho gọn gàng.

2.Cải tiến thứ 2


Bây giờ ta xét đến hàm thứ 2. Để cải tiến nó, chúng ta xem lại 1 chút cách toàn bộ script hoạt động. Khi cài đặt script này, chắc hẳn các bạn còn nhớ chúng ta phải cấu hình 1 chút cho nó:

thumbnail_mode = 'no-float';
summary_noimg = 430;
summary_img = 340;
img_thumb_height = 100;
img_thumb_width = 120;

Việc cấu hình này gồm 2 phần chính là cấu hình cho hình ảnh thumbnail (gồm chiều rộng và chiều cao, cũng như thuộc tính float) và cấu hình cho số lượng kí tự cần hiển thị. Phần cấu hình cho hình ảnh, ta có thể thấy là sau đó được gán vào thuộc tính style, widthheight khi sinh ra đoạn mã HTML (ở hàm thứ 2 createSummaryAndThumb). Sau này AnhVo có cải tiến chút ít là đưa tất cả các thuộc tính widthheight đó vào CSS (trong thẻ style).

Như vậy, nhận thấy rằng toàn bộ phần cấu hình dành cho hình ảnh đều thuộc về CSS. Vậy thì sao ta không gán cho chúng 1 lớp (class) để khỏi phải lặp lại nhiều lần ở các hình ảnh ở các bài viết? Điều này có 2 điều lợi: 1 là làm mã HTML của trang web sáng sủa, gọn gàng hơn vì CSS không bị gán trong từng thẻ IMG mà được rút ra thành 1 class; 2 là làm đoạn code javascript của chúng ta cũng đơn giản hơn (do không phải cấu hình nữa).

Như thế, tôi đã viết lại hàm thứ 2 này theo hướng đó, gán cho các hình ảnh thuộc tính CSS của lớp thumb. Ngoài ra, số kí tự hiển thị cũng được thay bằng số từ hiển thị (cho phù hợp với hàm đầu tiên mà tôi cải tiến), ở đây là 125 (xem cuối hàm).

function createSummaryAndThumb(id) {
 var p = document.getElementById(id),
  imgtag = '',
  img = p.getElementsByTagName('img');
 if (img.length >= 1) {
  imgtag = '<img class="thumb" src="' + img[0].src + '" />';
 }
 p.innerHTML = imgtag + stripTags(p.innerHTML, 125) + '...';
}

Công việc cuối cùng là bạn chỉ cần chỉnh sửa CSS cho class thumb thôi, VD như tôi làm như sau (chèn đoạn này vào phần trước ]]></b:skin> là được):

.thumb{float:left;display:inline;margin:5px 10px 10px 0;width:120px}

Như vậy, toàn bộ script Auto Readmore sẽ được viết lại như sau:

<script type='text/javascript'>
 //<![CDATA[
 function stripTags(s, n) {
  return s.replace(/<.*?>/ig, '').split(/\s+/).slice(0, n - 1).join(' ')
 }
 function createSummaryAndThumb(id) {
  var p = document.getElementById(id),
   imgtag = '',
   img = p.getElementsByTagName('img');
  if (img.length >= 1) {
   imgtag = '<img class="thumb" src="' + img[0].src + '" />';
  }
  p.innerHTML = imgtag + stripTags(p.innerHTML, 125) + '...';
 }
 //]]>
</script>

Đoạn mã này chèn trong thẻ HEAD, ngay trước </head>.

Trong template, để kích hoạt auto readmore, bạn tìm đến phần:

<data:post.body/>

Sửa nó thành:

<b:if cond='data:blog.pageType != "item"'>
 <span expr:id='"p" + data:post.id'><data:post.body/></span>
 <script type='text/javascript'>createSummaryAndThumb("p<data:post.id/>")</script>
 <a expr:href='data:post.url' title='Read more' rel="nofollow">Read more &rarr;</a>
<b:else/>
 <data:post.body/>
</b:if>

Hy vọng vài phần tích và cải tiến nhỏ ở trên có thể giúp các bạn hiểu phần nào đoạn mã mà mình đang dùng. Thân ái.

Cách cài đặt hack auto readmore này có chút không tương thích với tính năng viết Page của Blogger. Nếu bạn gặp phải lỗi này, có thể xem cách khắc phục tại đây.