Placeholder on IE8

Posted in Programming on October 30, 2015 by manhhomienbienthuy Comments
Placeholder on IE8

Việc phát triển một trang Web phải support những trình duyệt "cổ lỗ sĩ" như IE8 đúng là bài toán nan giải. Nhưng có những lúc chúng ta không làm không được. Trong bài viết này, tôi sẽ giới thiệu một kỹ thuật có thể làm IE8 hoạt động tính năng placeholder như các trình duyệt hiện đại.

Bối cảnh

Các trình duyệt version cũ IE 8 (và cả IE 9) không support thuộc tính placeholder với các thẻ input nên cho dù có thuộc tình này thì nó cũng không hoạt động. Và bây giờ có 1 yêu cầu là phải support thuộc tính này cho IE 8, 9.

Ý tưởng

Vì IE 8, 9 không support nên để support IE8, 9 có giao diện giống placeholder thì phải xử lý bằng JavaScript. Chúng ta sẽ thêm CSS để trông nó giống placeholder nhất.

một tools để làm việc này nhưng nó khá nặng nề và tôi cảm thấy nó cũng chẳng hiệu quả cho lắm. Thế nên tôi quyết định sẽ tự giải quyết bằng cách của mình.

Ý tưởng rất đơn giản. Khi người dùng không focus vào một input (event blur của JavaScript) thì tôi sẽ dùng JavaScript để set giá trị trường input đúng bằng placeholder của nó. Như vậy, placeholder sẽ hiện lên. Khi người dùng focus vào input thì tôi sẽ set giá trị của nó về rỗng để người dùng nhập liệu. Trong quá trình xử lý cần kiểm tra xem người dùng đã nhập dữ liệu chưa. Nếu đã nhập rồi thì không cần xử lý gì nữa. Ngoài ra, tôi sẽ dùng CSS để tạo hiệu ứng màu sắc giống với placeholder nhất có thể.

Cách làm

CSS

Tôi sẽ dùng class tên là placeholder để đặt màu cho text trong trường input sao cho nó giống với placeholder thật nhất. CSS sẽ như sau:

.placeholder {
  color: #aaa;
}

JavaScript

Trong bài viết này, tôi sẽ code bằng CoffeeScript, nó sẽ được compile ra JavaScript nên hoạt động của nó không có gì khác nhau cả.

Trước hết, cần kiểm tra xem trình duyệt đang dùng có phải IE 8, 9 hay không. Nếu là các trình duyệt hiện đại thì đương nhiên, chúng ta không cần làm gì cả, vì chúng đã support placeholder rồi. Chúng ta chỉ cần xử lý với IE 8, 9 mà thôi.

is_ie89 = navigator.appVersion.indexOf("MSIE 8.") != -1 ||
  navigator.appVersion.indexOf("MSIE 9.") != -1

Sau đó là quá trình xử lý, trước hết, khi người dùng focus vào một input thì cần kiểm tra người dùng đã nhập dữ liệu chưa. Nếu chưa thì xóa placeholder đi để người dùng nhập liệu mới.

$("form").on "focus", "[placeholder]", ->
  input = $(@)
  if input.val() == input.attr "placeholder"
    input.val ""
    input.removeClass "placeholder"
  return

Bây giờ, khi người dùng blur một trường input chúng ta cần show ra placeholder của nó. JavaScript sẽ xử lý bằng cách cho giá trị của input đúng bằng giá trị của placeholder, và thêm CSS cho nó giống placeholder thật.

$("form").on "blur", "[placeholder]", ->
  input = $(@)
  if input.val() == "" || input.val() == input.attr("placeholder")
    input.addClass "placeholder"
    input.val input.attr("placeholder")
  return

Và mặc định khi người dùng duyệt trang, tất cả các trường sẽ bị blur

$ ->
  $("[placeholder]").blur()

Cần thêm một xử lý nữa.  Đó là khi người dùng submit form. Nếu người dùng không điền gì mà submit thì dù người dùng chưa nhập gì, các trường input đều đã có dữ liệu. Nên nếu submit form, dữ liệu được gửi là placeholder của chúng.  Đây không phải là vấn đề lớn lắm, nhưng không nên bỏ qua nó.

$("[placeholder]").parents("form").submit ->
  $(@).find("[placeholder]").each ->
    input = $(@)
    if input.val() == input.attr("placeholder")
      input.val ""
    return
  return

Xử lý các trường type="password"

Với các trường type="password" thì khi người dùng nhập liệu sẽ không nhìn thấy dữ liệu mà chỉ thấy các chấm tròn mà thôi. Trong xử lý JavaScript ở trên cũng vậy. Nếu không xử lý thêm thì placeholder hiện ra cũng chỉ toàn là chấm tròn mà thôi. Nên với các inputtype="password" cần xử lý thêm một chút.

Có một điều khó chịu với IE 8, 9 là thuộc tính type của input chỉ có thể đọc mà không thể thay thế nó được. Nên quá trình xử lý hơi lằng nhằng, đó là chúng ta buộc phải thay thế toàn bộ outerHTML của trường input đó mới có tác dụng.

Xử lý khi focus trường password như sau:

if input.attr("id") == "user_password"
  document.getElementById("user_password").outerHTML = '<input
    class="form-control input-field" name="user[password]"
    placeholder="パスワードを入力してください" type="password">'
  $("[type=password]").focus()
  $("[type=password]").focus()

Xử lý này sẽ được lồng vào trong event focus đã có ở trên. Phải cẩn thận chỗ này, tôi đã loại bỏ id của input để điều kiện ở trên if input.attr("id") == "user_password" chỉ đúng 1 lần duy nhất khi chúng ta focus lần đầu tiên mà thôi. Sau khi thay outerHTML rồi thì nó sẽ không đúng nữa. Nếu không có thao tác này, điều kiện này luôn luôn đúng.  Đoạn code này sẽ chạy đi chạy lại mãi mà không dừng.

Có vẻ hơi ngu si một chút nhưng ở trên phải focus 2 lần vào input thì mới có tác dụng. Tôi không biết tại sao. IE 8 cần phải làm vậy trong khi các trình duyệt khác thì không cần.

Xử lý blur với trường password như sau:

if input.attr("name") == "user[password]" && input.val() == ""
  document.getElementsByName("user[password]")[0].outerHTML = '<input
    value="パスワードを入力してください" placeholder="パスワードを入力してください"
    class="form-control input-field placeholder" name="user[password]" id="user_password"
    type="text">'

Cũng tương tự như trên, tôi phải cẩn thận khi đặt điều kiện, nếu không đoạn code sẽ lặp đi lặp lại không dừng. Tôi không thể kiểm tra id vì tôi đã bỏ id khi focus rồi. Nên tôi cần kiểm tra thuộc tính khác. Và cũng tương tự các trường hợp khác, tôi cũng kiểm tra input đã có dữ liệu hay chưa.  Điều kiện này đồng thời cũng chỉ đúng 1 lần duy nhất là lần đầu tiên người dùng blur. Sau khi thay thế outerHTML, input đã có dữ liệu, chính là placeholder.

Đoạn code hoàn chỉnh như sau:

$ ->
  is_ie89 = navigator.appVersion.indexOf("MSIE 8.") != -1 ||
    navigator.appVersion.indexOf("MSIE 9.") != -1
  if is_ie89
    $("form").on "focus", "[placeholder]", ->
      input = $(@)
      if input.attr("id") == "user_password"
        document.getElementById("user_password").outerHTML = '<input
          class="form-control input-field" name="user[password]"
          placeholder="パスワードを入力してください" type="password">'
        $("[type=password]").focus()
        $("[type=password]").focus()
      if input.val() == input.attr "placeholder"
        input.val ""
        input.removeClass "placeholder"
      return
    $("form").on "blur", "[placeholder]", ->
      input = $(@)
      if input.attr("name") == "user[password]" && input.val() == ""
        document.getElementsByName("user[password]")[0].outerHTML = '<input
          value="パスワードを入力してください" placeholder="パスワードを入力してください"
          class="form-control input-field placeholder" name="user[password]" id="user_password"
          type="text">'
      if input.val() == "" || input.val() == input.attr("placeholder")
        input.addClass "placeholder"
        input.val input.attr("placeholder")
      return

    $("[placeholder]").parents("form").submit ->
      $(@).find("[placeholder]").each ->
        input = $(@)
        if input.val() == input.attr("placeholder")
          input.val ""
        return
      return
  return

$ ->
  $("[placeholder]").blur()

Update

Đoạn xử lý trên có một điểm yếu là nó chỉ sử dụng cho 1 trường password đã biết trước ID là user_password mà thôi. Trong trường hợp cần xử lý với nhiều trường password với các ID khác nhau thì bạn cần xử lý cho từng trường hợp giống như trường có ID user_password như trên.  Đây không phải là một ý tưởng tốt.

Vì vậy, đoạn chương trình JavaScript trên được update như sau, có thể áp dụng với tất cả các trường password có ở trên trang Web.

$ ->
  is_ie89 = navigator.appVersion.indexOf("MSIE 8.") != -1 ||
    navigator.appVersion.indexOf("MSIE 9.") != -1
  if is_ie89
    input_passwords = {}
    $("[placeholder][type=password]").each ->
      input_passwords[@id] = $(@)[0].outerHTML
      return

    $("form").on "focus", "[placeholder]", ->
      if input_passwords[@id] && $(@).attr("type") == "text"
        document.getElementById(@id).outerHTML = input_passwords[@id]
        $("#" + @id).focus().focus()
        $("#" + @id).val @value

      if @value == $(@).attr "placeholder"
        $("#" + @id).val ""
        $("#" + @id).removeClass "placeholder"
      return

    $("form").on "blur", "[placeholder]", ->
      if input_passwords[@id] && @value == ""
        document.getElementById(@id).outerHTML = input_passwords[@id]
          .replace(/type="?password"?/g, 'type="text"')

      if @value == "" || @value == $(@).attr("placeholder")
        $("#" + @id).addClass "placeholder"
        $("#" + @id).val $(@).attr("placeholder")
      return

    $("[placeholder]").parents("form").submit ->
      $(@).find("[placeholder]").each ->
        input = $(@)
        if input.val() == input.attr("placeholder")
          input.val ""
        return
      return
    $("[placeholder]").blur()

  return

Đoạn code trên là CoffeeScript, nó sẽ được convert ra JavaScript. Chỉ cần nhúng đoạn JavaScript đó với trang Web là nó sẽ hoạt động.

Với xử lý như trên. Mặc dù nó hoạt động không hoàn toàn giống placeholder trên các trình duyệt hiện đại, nhưng cũng có thể chấp nhận được.

demo

I apologise for any typos. If you notice a problem, please let me know.

Thank you all for your attention.