带有事件的简单 Javascript 日历

欢迎来到关于如何创建简单的纯 Javascript 日历的教程。您是否希望在不使用任何服务器端脚本的情况下开发日历 Web 应用程序?无论是商业项目、学校项目还是只是出于好奇——您来对地方了。本指南将引导您了解如何创建纯 Javascript 日历,所有这些都可以通过本地存储实现。继续阅读!

ⓘ 我在本教程开始时包含了一个包含所有源代码的 zip 文件,因此您不必复制粘贴所有内容……或者如果您只是想直接进入。

 

速记

  • 如果您想将日历设置为从星期一开始一周,请cal.sMon = truecalendar.js.

如果您发现错误,请随时在下面发表评论。我也尝试回答简短的问题,但这是一个人与整个世界的对比……如果您急需答案,请查看我的网站列表以获取编程帮助

 

示例代码下载

单击此处下载源代码,我已在 MIT 许可下发布它,因此请随意在其上构建或在您自己的项目中使用它。

 

JAVASCRIPT 日历演示

 

日历如何运作

好的,现在让我们更详细地了解日历的工作原理。不打算逐行解释,但这里有一个快速演练。

 

第 1 部分)日历 HTML

日历.html
<div id="cal-wrap">
  <!-- (A) PERIOD SELECTOR -->
  <div id="cal-date">
    <select id="cal-mth"></select>
    <select id="cal-yr"></select>
  </div>

  <!-- (B) CALENDAR -->
  <div id="cal-container"></div>

  <!-- (C) EVENT FORM -->
  <form id="cal-event">
    <h1 id="evt-head"></h1>
    <div id="evt-date"></div>
    <textarea id="evt-details" required></textarea>
    <input id="evt-close" type="button" value="Close"/>
    <input id="evt-del" type="button" value="Delete"/>
    <input id="evt-save" type="submit" value="Save"/>
  </form>
</div>

HTML 应该足够简单,这里只有 3 个部分:

  • <div id="cal-date">月份和年份选择器。
  • <div id="cal-container">我们将在其中显示所选月份和年份的日历。
  • <form id="cal-event">添加/编辑日历事件的表单。

 

 

第 2 部分)日历初始化

日历.js
var cal = {
  // (A) PROPERTIES
  // (A1) COMMON CALENDAR
  sMon : false, // Week start on Monday?
  mName : ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], // Month Names
 
  // (A2) CALENDAR DATA
  data : null, // Events for the selected period
  sDay : 0, sMth : 0, sYear : 0, // Current selected day, month, year
 
  // (A3) COMMON HTML ELEMENTS
  hMth : null, hYear : null, // month/year selector
  hForm : null, hfHead : null, hfDate : null, hfTxt : null, hfDel : null, // event form
 
  // (B) INIT CALENDAR
  init : () => {
    // (B1) GET + SET COMMON HTML ELEMENTS
    cal.hMth = document.getElementById("cal-mth");
    cal.hYear = document.getElementById("cal-yr");
    cal.hForm = document.getElementById("cal-event");
    cal.hfHead = document.getElementById("evt-head");
    cal.hfDate = document.getElementById("evt-date");
    cal.hfTxt = document.getElementById("evt-details");
    cal.hfDel = document.getElementById("evt-del");
    document.getElementById("evt-close").onclick = cal.close;
    cal.hfDel.onclick = cal.del;
    cal.hForm.onsubmit = cal.save;
 
    // (B2) DATE NOW
    let now = new Date(),
        nowMth = now.getMonth(),
        nowYear = parseInt(now.getFullYear());
 
    // (B3) APPEND MONTHS SELECTOR
    for (let i=0; i<12; i++) {
      let opt = document.createElement("option");
      opt.value = i;
      opt.innerHTML = cal.mName[i];
      if (i==nowMth) { opt.selected = true; }
      cal.hMth.appendChild(opt);
    }
    cal.hMth.onchange = cal.list;
 
    // (B4) APPEND YEARS SELECTOR
    // Set to 10 years range. Change this as you like.
    for (let i=nowYear-10; i<=nowYear+10; i++) {
      let opt = document.createElement("option");
      opt.value = i;
      opt.innerHTML = i;
      if (i==nowYear) { opt.selected = true; }
      cal.hYear.appendChild(opt);
    }
    cal.hYear.onchange = cal.list;
 
    // (B5) START - DRAW CALENDAR
    cal.list();
};
window.addEventListener("load", cal.init);

cal.init()是在页面加载时运行以初始化日历的第一件事。乍一看很复杂,但要冷静仔细看——它所做的只是设置HTML界面:

  • 将月份添加到选择器。
  • 向选择器添加年份。
  • 附加添加/编辑事件表单单击并提交处理程序。

就这样。

 

 

第 3 部分)绘制日历

日历.js
// (C) DRAW CALENDAR FOR SELECTED MONTH
list : () => {
  // (C1) BASIC CALCULATIONS - DAYS IN MONTH, START + END DAY
  // Note - Jan is 0 & Dec is 11
  // Note - Sun is 0 & Sat is 6
  cal.sMth = parseInt(cal.hMth.value); // selected month
  cal.sYear = parseInt(cal.hYear.value); // selected year
  let daysInMth = new Date(cal.sYear, cal.sMth+1, 0).getDate(), // number of days in selected month
      startDay = new Date(cal.sYear, cal.sMth, 1).getDay(), // first day of the month
      endDay = new Date(cal.sYear, cal.sMth, daysInMth).getDay(), // last day of the month
      now = new Date(), // current date
      nowMth = now.getMonth(), // current month
      nowYear = parseInt(now.getFullYear()), // current year
      nowDay = cal.sMth==nowMth && cal.sYear==nowYear ? now.getDate() : null ;
 
  // (C2) LOAD DATA FROM LOCALSTORAGE
  cal.data = localStorage.getItem("cal-" + cal.sMth + "-" + cal.sYear);
  if (cal.data==null) {
  localStorage.setItem("cal-" + cal.sMth + "-" + cal.sYear, "{}");
  cal.data = {};
  } else { cal.data = JSON.parse(cal.data); }
 
  // (C3) DRAWING CALCULATIONS
  // Blank squares before start of month
  let squares = [];
  if (cal.sMon && startDay != 1) {
    let blanks = startDay==0 ? 7 : startDay ;
    for (let i=1; i<blanks; i++) { squares.push("b"); }
  }
  if (!cal.sMon && startDay != 0) {
    for (let i=0; i<startDay; i++) { squares.push("b"); }
  }
 
  // Days of the month
  for (let i=1; i<=daysInMth; i++) { squares.push(i); }
 
  // Blank squares after end of month
  if (cal.sMon && endDay != 0) {
    let blanks = endDay==6 ? 1 : 7-endDay;
    for (let i=0; i<blanks; i++) { squares.push("b"); }
  }
  if (!cal.sMon && endDay != 6) {
    let blanks = endDay==0 ? 6 : 6-endDay;
    for (let i=0; i<blanks; i++) { squares.push("b"); }
  }
 
  // (C4) DRAW HTML CALENDAR
  // Get container
  let container = document.getElementById("cal-container"),
      cTable = document.createElement("table");
  cTable.id = "calendar";
  container.innerHTML = "";
  container.appendChild(cTable);

  // First row - Day names
  let cRow = document.createElement("tr"),
      days = ["Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"];
  if (cal.sMon) { days.push(days.shift()); }
  for (let d of days) {
    let cCell = document.createElement("td");
    cCell.innerHTML = d;
    cRow.appendChild(cCell);
  }
  cRow.classList.add("head");
  cTable.appendChild(cRow);

  // Days in Month
  let total = squares.length;
  cRow = document.createElement("tr");
  cRow.classList.add("day");
  for (let i=0; i<total; i++) {
    let cCell = document.createElement("td");
    if (squares[i]=="b") { cCell.classList.add("blank"); }
    else {
      if (nowDay==squares[i]) { cCell.classList.add("today"); }
      cCell.innerHTML = `<div class="dd">${squares[i]}</div>`;
      if (cal.data[squares[i]]) {
        cCell.innerHTML += "<div class='evt'>" + cal.data[squares[i]] + "</div>";
      }
      cCell.onclick = () => { cal.show(cCell); };
    }
    cRow.appendChild(cCell);
    if (i!=0 && (i+1)%7==0) {
      cTable.appendChild(cRow);
      cRow = document.createElement("tr");
      cRow.classList.add("day");
    }
  }
 
  // (C5) REMOVE ANY PREVIOUS ADD/EDIT EVENT DOCKET
  cal.close();
}

cal.list()绘制当前选择的月/年的日历,是本项目中最复杂的功能。长话短说:

  • (C1 & C2)事件数据localStorage按月保存 ( cal-MONTH-YEAR = JSON ENCODED OBJECT)。
  • (C1 & C2)检索所选月/年的事件数据,并将其放入cal.data.
  • (C3)首先进行所有计算squares = []
  • (C4)然后,循环squares绘制 HTML 日历。

 

 

第 4 部分)显示/编辑日历事件

日历.js
// (D) SHOW EDIT EVENT DOCKET FOR SELECTED DAY
show : (el) => {
  // (D1) FETCH EXISTING DATA
  cal.sDay = el.getElementsByClassName("dd")[0].innerHTML;
  let isEdit = cal.data[cal.sDay] !== undefined ;

  // (D2) UPDATE EVENT FORM
  cal.hfTxt.value = isEdit ? cal.data[cal.sDay] : "" ;
  cal.hfHead.innerHTML = isEdit ? "EDIT EVENT" : "ADD EVENT" ;
  cal.hfDate.innerHTML = `${cal.sDay} ${cal.mName[cal.sMth]} ${cal.sYear}`;
  if (isEdit) { cal.hfDel.classList.remove("ninja"); }
  else { cal.hfDel.classList.add("ninja"); }
  cal.hForm.classList.remove("ninja");
},

// (E) CLOSE EVENT DOCKET
close : () => {
  cal.hForm.classList.add("ninja");
}

当用户点击一个日期单元格时,cal.show()将被调用。不言自明——我们从 获取事件cal.data,然后显示添加/编辑事件表单。

 

第 5 部分)保存/删除事件数据

日历.js
// (F) SAVE EVENT
save : () => {
  cal.data[cal.sDay] = cal.hfTxt.value;
  localStorage.setItem(`cal-${cal.sMth}-${cal.sYear}`, JSON.stringify(cal.data));
  cal.list();
  return false;
},

// (G) DELETE EVENT FOR SELECTED DATE
del : () => { if (confirm("Delete event?")) {
  delete cal.data[cal.sDay];
  localStorage.setItem(`cal-${cal.sMth}-${cal.sYear}`, JSON.stringify(cal.data));
  cal.list();
}

最后,这些都是不言自明的函数——我们只是简单地更新localStorage.

 

 

有用的信息和链接

最后,这里还有一些可能有用的附加功能。

 

每天有多个事件?

我们当然可以。我们来看看当前的save()函数:

cal.data[cal.sDay] = cal.hfTxt.value;

我们现在几乎存储了事件数据data[DAY] = EVENT。所以要允许同一天发生多个事件,大体思路是把当前结构变成多维数组data[DAY] = [EVENT, EVENT, EVENT]

cal.data[cal.sDay] = [];
cal.data[cal.sDay].push(document.getElementById("evt-details").value);

那么,大问题来了。我们必须重做所有其余的功能才能正确绘制多个事件并管理它们。

  • 在 HTML 中添加更多文本字段。
  • save()包括额外的文本字段。
  • list()绘制额外的数据。
  • show()也填充额外的文本字段。

你可以猜到,这将成倍增加复杂性,把它变成一个“不简单的教程”。这就是为什么我不回答“支持多个事件”和“跨越多天的事件”问题的原因——尽管请随意挑战自己,这个简单的日历是一个很好的起点。

 

限制

  • 一天只允许一个事件。
  • 事件不能跨越多天。
  • Javascript 日历需要使用本地存储。如果它在浏览器上被禁用或不受支持,那么它将根本无法保存事件。

但是,当然,我已将其作为开源发布。因此,请随意调整它,但您认为合适。

完整汉化源码

calendar.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Simple Javascript Calendar</title>
        <link href="calendar.css" rel="stylesheet">
        <script async src="calendar.js"></script>
    </head>
    <body>
        <div id="cal-wrap">
            <!-- (A) PERIOD SELECTOR -->
            <div id="cal-date">
                <select id="cal-yr"></select>
                <select id="cal-mth"></select>
            </div>

            <!-- (B) CALENDAR -->
            <div id="cal-container"></div>

            <!-- (C) EVENT FORM -->
            <form id="cal-event">
                <h1 id="evt-head"></h1>
                <div id="evt-date"></div>
                <textarea id="evt-details" required></textarea>
                <input id="evt-close" type="button" value="Close"/>
                <input id="evt-del" type="button" value="Delete"/>
                <input id="evt-save" type="submit" value="Save"/>
            </form>
        </div>
    </body>
</html>


calendar.css

/* (A) ENTIRE PAGE */
#cal-wrap * {
    font-family: arial, sans-serif;
}
.ninja {
    display: none !important;
}

/* (B) CONTAINER */
#cal-wrap {
    max-width: 600px;
}

/* (C) PERIOD SELECTOR */
#cal-date {
    display: flex;
    justify-content: space-between;
}
#cal-mth, #cal-yr {
    box-sizing: border-box;
    padding: 10px 20px;
    font-size: 1.2em;
    border: 0;
}

/* (D) CALENDAR */
#calendar {
    width: 100%;
    border-collapse: collapse;
}
#calendar tr.head td {
    font-weight: bold;
    text-transform: uppercase;
    color: #fff;
    background: #f37070;
    padding: 15px;
    text-align: center;
}
#calendar tr.day td {
    border: 1px solid #ddd;
    width: 14.28%;
    padding: 15px 5px;
    vertical-align: top;
}
#calendar tr.day td:hover {
    background: #fff9e4;
    cursor: pointer;
}
#calendar tr td.blank {
    background: #f5f5f5;
}
#calendar tr td.today {
    background: #ffdede;
}
#calendar .dd {
    font-size: 1.2em;
    color: #999;
}
#calendar .evt {
    margin-top: 5px;
    font-size: 0.8em;
    font-weight: bold;
    overflow: hidden;
    color: #ff5d5d;
}

/* (E) ADD/EDIT EVENT */
#cal-event {
    padding: 15px;
    margin-top: 20px;
    background: #f5f5f5;
    border: 1px solid #ddd;
}
#cal-event h1 {
    color: #333;
    padding: 0;
    margin: 0;
}
#evt-date {
    color: #555;
    margin: 10px 0;
}
#cal-event textarea {
    display: block;
    box-sizing: border-box;
    width: 100%;
    padding: 10px;
    margin: 10px 0;
    border: 1px solid #ddd;
    min-height: 100px;
}
#cal-event input[type=button], #cal-event input[type=submit] {
    padding: 10px;
    margin: 5px;
    font-size: 1.2em;
    border: 0;
    background: #ea4c4c;
    color: #fff;
}


calendar.js

var cal = {
    // (A) PROPERTIES
    // (A1) COMMON CALENDAR
    sMon: true, // Week start on Monday?
    mName: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"], // Month Names

    // (A2) CALENDAR DATA
    data: null, // Events for the selected period
    sDay: 0, sMth: 0, sYear: 0, // Current selected day, month, year

    // (A3) COMMON HTML ELEMENTS
    hMth: null, hYear: null, // month/year selector
    hForm: null, hfHead: null, hfDate: null, hfTxt: null, hfDel: null, // event form

    // (B) INIT CALENDAR
    init: () => {
        // (B1) GET + SET COMMON HTML ELEMENTS
        cal.hMth = document.getElementById("cal-mth");
        cal.hYear = document.getElementById("cal-yr");
        cal.hForm = document.getElementById("cal-event");
        cal.hfHead = document.getElementById("evt-head");
        cal.hfDate = document.getElementById("evt-date");
        cal.hfTxt = document.getElementById("evt-details");
        cal.hfDel = document.getElementById("evt-del");
        document.getElementById("evt-close").onclick = cal.close;
        cal.hfDel.onclick = cal.del;
        cal.hForm.onsubmit = cal.save;

        // (B2) DATE NOW
        let now = new Date(),
                nowMth = now.getMonth(),
                nowYear = parseInt(now.getFullYear());

        // (B3) APPEND MONTHS SELECTOR
        for (let i = 0; i < 12; i++) {
            let opt = document.createElement("option");
            opt.value = i;
            opt.innerHTML = cal.mName[i];
            if (i == nowMth) {
                opt.selected = true;
            }
            cal.hMth.appendChild(opt);
        }
        cal.hMth.onchange = cal.list;

        // (B4) APPEND YEARS SELECTOR
        // Set to 10 years range. Change this as you like.
        for (let i = nowYear - 10; i <= nowYear + 10; i++) {
            let opt = document.createElement("option");
            opt.value = i;
            opt.innerHTML = i;
            if (i == nowYear) {
                opt.selected = true;
            }
            cal.hYear.appendChild(opt);
        }
        cal.hYear.onchange = cal.list;

        // (B5) START - DRAW CALENDAR
        cal.list();
    },

    // (C) DRAW CALENDAR FOR SELECTED MONTH
    list: () => {
        // (C1) BASIC CALCULATIONS - DAYS IN MONTH, START + END DAY
        // Note - Jan is 0 & Dec is 11
        // Note - Sun is 0 & Sat is 6
        cal.sMth = parseInt(cal.hMth.value); // selected month
        cal.sYear = parseInt(cal.hYear.value); // selected year
        let daysInMth = new Date(cal.sYear, cal.sMth + 1, 0).getDate(), // number of days in selected month
                startDay = new Date(cal.sYear, cal.sMth, 1).getDay(), // first day of the month
                endDay = new Date(cal.sYear, cal.sMth, daysInMth).getDay(), // last day of the month
                now = new Date(), // current date
                nowMth = now.getMonth(), // current month
                nowYear = parseInt(now.getFullYear()), // current year
                nowDay = cal.sMth == nowMth && cal.sYear == nowYear ? now.getDate() : null;

        // (C2) LOAD DATA FROM LOCALSTORAGE
        cal.data = localStorage.getItem("cal-" + cal.sMth + "-" + cal.sYear);
        if (cal.data == null) {
            localStorage.setItem("cal-" + cal.sMth + "-" + cal.sYear, "{}");
            cal.data = {};
        } else {
            cal.data = JSON.parse(cal.data);
        }

        // (C3) DRAWING CALCULATIONS
        // Blank squares before start of month
        let squares = [];
        if (cal.sMon && startDay != 1) {
            let blanks = startDay == 0 ? 7 : startDay;
            for (let i = 1; i < blanks; i++) {
                squares.push("b");
            }
        }
        if (!cal.sMon && startDay != 0) {
            for (let i = 0; i < startDay; i++) {
                squares.push("b");
            }
        }

        // Days of the month
        for (let i = 1; i <= daysInMth; i++) {
            squares.push(i);
        }

        // Blank squares after end of month
        if (cal.sMon && endDay != 0) {
            let blanks = endDay == 6 ? 1 : 7 - endDay;
            for (let i = 0; i < blanks; i++) {
                squares.push("b");
            }
        }
        if (!cal.sMon && endDay != 6) {
            let blanks = endDay == 0 ? 6 : 6 - endDay;
            for (let i = 0; i < blanks; i++) {
                squares.push("b");
            }
        }

        // (C4) DRAW HTML CALENDAR
        // Get container
        let container = document.getElementById("cal-container"),
                cTable = document.createElement("table");
        cTable.id = "calendar";
        container.innerHTML = "";
        container.appendChild(cTable);

        // First row - Day names
        let cRow = document.createElement("tr"),
                days = ["日", "一", "二", "三", "四", "五", "六"];
        if (cal.sMon) {
            days.push(days.shift());
        }
        for (let d of days) {
            let cCell = document.createElement("td");
            cCell.innerHTML = d;
            cRow.appendChild(cCell);
        }
        cRow.classList.add("head");
        cTable.appendChild(cRow);

        // Days in Month
        let total = squares.length;
        cRow = document.createElement("tr");
        cRow.classList.add("day");
        for (let i = 0; i < total; i++) {
            let cCell = document.createElement("td");
            if (squares[i] == "b") {
                cCell.classList.add("blank");
            } else {
                if (nowDay == squares[i]) {
                    cCell.classList.add("today");
                }
                cCell.innerHTML = `<div class="dd">${squares[i]}</div>`;
                if (cal.data[squares[i]]) {
                    cCell.innerHTML += "<div class='evt'>" + cal.data[squares[i]] + "</div>";
                }
                cCell.onclick = () => {
                    cal.show(cCell);
                };
            }
            cRow.appendChild(cCell);
            if (i != 0 && (i + 1) % 7 == 0) {
                cTable.appendChild(cRow);
                cRow = document.createElement("tr");
                cRow.classList.add("day");
            }
        }

        // (C5) REMOVE ANY PREVIOUS ADD/EDIT EVENT DOCKET
        cal.close();
    },

    // (D) SHOW EDIT EVENT DOCKET FOR SELECTED DAY
    show: (el) => {
        // (D1) FETCH EXISTING DATA
        cal.sDay = el.getElementsByClassName("dd")[0].innerHTML;
        let isEdit = cal.data[cal.sDay] !== undefined;

        // (D2) UPDATE EVENT FORM
        cal.hfTxt.value = isEdit ? cal.data[cal.sDay] : "";
        cal.hfHead.innerHTML = isEdit ? "EDIT EVENT" : "ADD EVENT";
        cal.hfDate.innerHTML = `${cal.sDay} ${cal.mName[cal.sMth]} ${cal.sYear}`;
        if (isEdit) {
            cal.hfDel.classList.remove("ninja");
        } else {
            cal.hfDel.classList.add("ninja");
        }
        cal.hForm.classList.remove("ninja");
    },

    // (E) CLOSE EVENT DOCKET
    close: () => {
        cal.hForm.classList.add("ninja");
    },

    // (F) SAVE EVENT
    save: () => {
        cal.data[cal.sDay] = cal.hfTxt.value;
        localStorage.setItem(`cal-${cal.sMth}-${cal.sYear}`, JSON.stringify(cal.data));
        cal.list();
        return false;
    },

    // (G) DELETE EVENT FOR SELECTED DATE
    del: () => {
        if (confirm("Delete event?")) {
            delete cal.data[cal.sDay];
            localStorage.setItem(`cal-${cal.sMth}-${cal.sYear}`, JSON.stringify(cal.data));
            cal.list();
        }
    }
};
window.addEventListener("load", cal.init);


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值