Hướng dẫn tạo hộp chọn ngày Datepicker bằng css & js không dùng thư viện

61 Thích Comment

Chắc chắn có rất nhiều bạn sử dụng thư viện hoặc plugin để tạo datepicker như jQuery, Boostrap,… Nhưng bạn cũng hiểu được rằng chúng ta sẽ không dùng hết tính năng trong thư viện/plugin đó gây ra lãng phí tài nguyên. Vì vậy trong bài viết này sharescript sẽ hướng dẫn cách tạo datepicker chỉ sử dụng css và javascript.

Mình đã nén source và chia sẻ ở link download cuối bài viết, bạn có thể cuộn xuống dưới tải code về trước sau đó đọc bài viết để dễ hiểu hơn.

Tạo datepicker đơn giản

1) Tạo file index.html

<!DOCTYPE html>
<html>
  <head>
    <title>
      Date Picker Options
    </title>

    <!-- (A) LOAD THE LIBRARIES -->
    <link href="dp-dark.css" rel="stylesheet">
    <!-- <link href="dp-light.css" rel="stylesheet"> -->
    <script src="datepicker.js"></script>
  </head>
  <body>
    <!-- (B) THE HTML -->
    <h1>Date Picker Options</h1>
    <input type="text" id="input-opt"/>
    <div id="pick-opt"></div>

    <!-- (C) ATTACH DATE PICKER ON LOAD -->
    <script>
    window.addEventListener("load", function(){
      picker.attach({
        target: "input-opt",
        container: "pick-opt",
        disableday : [2, 7], // DISABLE TUE, SUN
        startmon: true // WEEK START ON MON
      });
    });
    </script>
  </body>
</html>

2) Tạo file dp-dark.css

/* (A) POPUP */
.picker-wrap {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: rgba(0,0,0,0.5);
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s;
}
.picker-wrap.show {
  opacity: 1;
  visibility: visible;
}
.picker-wrap .picker {
  margin: 50vh auto 0 auto;
  transform: translateY(-50%);
}

/* (B) CONTAINER */
.picker {
  max-width: 300px;
  border: 1px solid #000;
  background: #444;
  padding: 10px;
}

/* (C) MONTH + YEAR */
.picker-m, .picker-y {
  width: 50%;
  padding: 5px;
  box-sizing: border-box;
  font-size: 16px;
}

/* (D) DAY */
.picker-d table {
  color: #fff;
  border-collapse: separate;
  width: 100%;
  margin-top: 10px;
}
.picker-d table td {
  width: 14.28%; /* 7 EQUAL COLUMNS */
  padding: 5px;
  text-align: center;
}
/* HEADER CELLS */
.picker-d-h td {
  font-weight: bold;
}
/* BLANK DATES */
.picker-d-b {
  background: #4e4e4e;
}
/* TODAY */
.picker-d-td {
  background: #d84f4f;
}
/* PICKABLE DATES */
.picker-d-d:hover {
  cursor: pointer;
  background: #a33c3c;
}
/* UNPICKABLE DATES */
.picker-d-dd {
  color: #888;
  background: #4e4e4e;
}

3) Tạo file datepicker.js

var picker = {
  // (A) ATTACH DATEPICKER TO TARGET
  // target : datepicker will populate this field
  // container : datepicker will be generated in this container
  // startmon : start on Monday (default false)
  // disableday : array of days to disable, e.g. [2,7] to disable Tue and Sun
  attach : function (opt) {
    // (A1) CREATE NEW DATEPICKER
    var dp = document.createElement("div");
    dp.dataset.target = opt.target;
    dp.dataset.startmon = opt.startmon ? "1" : "0";
    dp.classList.add("picker");
    if (opt.disableday) {
      dp.dataset.disableday = JSON.stringify(opt.disableday);
    }

    // (A2) DEFAULT TO CURRENT MONTH + YEAR - NOTE: UTC+0!
    var today = new Date(),
        thisMonth = today.getUTCMonth(), // Note: Jan is 0
        thisYear = today.getUTCFullYear(),
        months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];

    // (A3) MONTH SELECT
    var select = document.createElement("select"),
        option = null;
    select.classList.add("picker-m");
    for (var mth in months) {
      option = document.createElement("option");
      option.value = parseInt(mth) + 1;
      option.text = months[mth];
      select.appendChild(option);
    }
    select.selectedIndex = thisMonth;
    select.addEventListener("change", function(){ picker.draw(this); });
    dp.appendChild(select);

    // (A4) YEAR SELECT
    var yRange = 10; // Year range to show, I.E. from thisYear-yRange to thisYear+yRange
    select = document.createElement("select");
    select.classList.add("picker-y");
    for (var y = thisYear-yRange; y < thisYear+yRange; y++) {
      option = document.createElement("option");
      option.value = y;
      option.text = y;
      select.appendChild(option);
    }
    select.selectedIndex = yRange;
    select.addEventListener("change", function(){ picker.draw(this); });
    dp.appendChild(select);

    // (A5) DAY SELECT
    var days = document.createElement("div");
    days.classList.add("picker-d");
    dp.appendChild(days);

    // (A6) ATTACH DATE PICKER TO TARGET CONTAINER + DRAW THE DATES
    picker.draw(select);

    // (A6-I) INLINE DATE PICKER
    if (opt.container) { document.getElementById(opt.container).appendChild(dp); }

    // (A6-P) POPUP DATE PICKER
    else {
      // (A6-P-1) MARK THIS AS A "POPUP"
      var uniqueID = 0;
      while (document.getElementById("picker-" + uniqueID) != null) {
        uniqueID = Math.floor(Math.random() * (100 - 2)) + 1;
      }
      dp.dataset.popup = "1";
      dp.dataset.dpid = uniqueID;

      // (A6-P-2) CREATE WRAPPER
      var wrapper = document.createElement("div");
      wrapper.id = "picker-" + uniqueID;
      wrapper.classList.add("picker-wrap");
      wrapper.appendChild(dp);

      // (A6-P-3) ATTACH ONCLICK TO SHOW/HIDE DATEPICKER
      var target = document.getElementById(opt.target);
      target.dataset.dp = uniqueID;
      target.readOnly = true; // Prevent onscreen keyboar on mobile devices
      target.onfocus = function () {
        document.getElementById("picker-" + this.dataset.dp).classList.add("show");
      };
      wrapper.addEventListener("click", function (evt) {
        if (evt.target.classList.contains("picker-wrap")) {
          this.classList.remove("show");
        }
      });

      // (A6-P-4) ATTACH POPUP DATEPICKER TO BODY
      document.body.appendChild(wrapper);
    }
  },


  // (B) DRAW THE DAYS IN MONTH
  // el : HTML reference to either year or month selector
  draw : function (el) {
    // (B1) GET DATE PICKER COMPONENTS
    var parent = el.parentElement,
        year = parent.getElementsByClassName("picker-y")[0].value,
        month = parent.getElementsByClassName("picker-m")[0].value,
        days = parent.getElementsByClassName("picker-d")[0];

    // (B2) DATE RANGE CALCULATION - NOTE: UTC+0!
    var daysInMonth = new Date(Date.UTC(year, month, 0)).getUTCDate(),
        startDay = new Date(Date.UTC(year, month-1, 1)).getUTCDay(), // Note: Sun = 0
        endDay = new Date(Date.UTC(year, month-1, daysInMonth)).getUTCDay(),
        startDay = startDay==0 ? 7 : startDay,
        endDay = endDay==0 ? 7 : endDay;

    // (B3) GENERATE DATE SQUARES (IN ARRAY FIRST)
    var squares = [],
        disableday = null;
    if (parent.dataset.disableday) {
      disableday = JSON.parse(parent.dataset.disableday);
    }

    // (B4) EMPTY SQUARES BEFORE FIRST DAY OF MONTH
    if (parent.dataset.startmon=="1" && startDay!=1) {
      for (var i=1; i<startDay; i++) { squares.push("B"); }
    }
    if (parent.dataset.startmon=="0" && startDay!=7) {
      for (var i=0; i<startDay; i++) { squares.push("B"); }
    }

    // (B5) DAYS OF MONTH
    // (B5-1) ALL DAYS ENABLED, JUST ADD
    if (disableday==null) {
      for (var i=1; i<=daysInMonth; i++) { squares.push([i, false]);  }
    }

    // (B5-2) SOME DAYS DISABLED
    else {
      var thisday = startDay;
      for (var i=1; i<=daysInMonth; i++) {
        // CHECK IF DAY IS DISABLED
        var disabled = disableday.includes(thisday);
        // DAY OF MONTH, DISABLED
        squares.push([i, disabled]); 
        // NEXT DAY
        thisday++;
        if (thisday==8) { thisday = 1; }
      }
    }

    // (B6) EMPTY SQUARES AFTER LAST DAY OF MONTH
    if (parent.dataset.startmon=="1" && endDay!=7) {
      for (var i=endDay; i<7; i++) { squares.push("B"); }
    }
    if (parent.dataset.startmon=="0" && endDay!=6) {
      for (var i=endDay; i<(endDay==7?13:6); i++) { squares.push("B"); }
    }

    // (B7) DRAW HTML
    var daynames = ["Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
    if (parent.dataset.startmon=="1") { daynames.push("Sun"); }
    else { daynames.unshift("Sun"); }

    // (B7-1) HTML DATE HEADER
    var table = document.createElement("table"),
        row = table.insertRow()
        cell = null;
    row.classList.add("picker-d-h");
    for (let d of daynames) {
      cell = row.insertCell();
      cell.innerHTML = d;
    }

    // (B7-2) HTML DATE CELLS
    var total = squares.length,
        row = table.insertRow(),
        today = new Date(),
        todayDate = null;
    if (today.getUTCMonth()+1 == month && today.getUTCFullYear() == year) {
      todayDate = today.getUTCDate();
    }
    for (var i=0; i<total; i++) {
      if (i!=total && i%7==0) { row = table.insertRow(); }
      cell = row.insertCell();
      if (squares[i] == "B") { 
        cell.classList.add("picker-d-b"); 
      } else { 
        cell.innerHTML = squares[i][0]; 
        // NOT ALLOWED TO CHOOSE THIS DAY
        if (squares[i][1]) {
          cell.classList.add("picker-d-dd");
        }
        // ALLOWED TO CHOOSE THIS DAY
        else {
          if (i == todayDate) { cell.classList.add("picker-d-td"); }
          cell.classList.add("picker-d-d");
          cell.addEventListener("click", function(){ picker.pick(this); });
        }
      }
    }

    // (B7-3) ATTACH NEW CALENDAR TO DATEPICKER
    days.innerHTML = "";
    days.appendChild(table);
  },

  // (C) CHOOSE A DATE
  // el : HTML reference to selected date cell
  pick : function (el) {
    // (C1) GET ALL COMPONENTS
    var parent = el.parentElement;
    while (!parent.classList.contains("picker")) {
      parent = parent.parentElement;
    }

    // (C2) GET FULL SELECTED YEAR MONTH DAY
    var year = parent.getElementsByClassName("picker-y")[0].value,
        month = parent.getElementsByClassName("picker-m")[0].value,
        day = el.innerHTML;

    // YYYY-MM-DD FORMAT - CHANGE FORMAT HERE IF YOU WANT !
    if (parseInt(month)<10) { month = "0" + month; }
    if (parseInt(day)<10) { day = "0" + day; }
    var fullDate = year + "-" + month + "-" + day;

    // (C3) UPDATE SELECTED DATE
    document.getElementById(parent.dataset.target).value = fullDate;

    // (C4) POPUP ONLY - CLOSE THE POPUP
    if (parent.dataset.popup == "1") {
      document.getElementById("picker-" + parent.dataset.dpid).classList.remove("show");
    }
  }
};

Tùy chọn file index.html:

disableday : [2, 7]: Không cho người dùng chọn các ngày thứ 3 và chủ nhật, ví dụ [1, 5] sẽ là thứ 2 và thứ 6

startmon: true: Tuần được bắt đầu từ thứ 2, nếu đổi giá trị thành false thì tuần bắt đầu từ chủ nhật

Và còn rất nhiều tính năng khác bạn có thể tự tìm hiểu!

Bình luận bằng facebook
Có thể bạn thích
Tác giả: Sharescript.net