├── .gitignore ├── default.css ├── .gitattributes ├── changes.txt ├── screenshot.png ├── release ├── js_calendar_1_0.tar.gz └── js_calendar_1_1.tar.gz ├── Makefile ├── calendar_db_null.js ├── calendar_null.html ├── calendar_cookie.html ├── calendar_cgi_pstore.html ├── cgi_pstore ├── list.rb ├── utils.rb ├── list_icalendar.rb └── modify.rb ├── calendar.html ├── calendar_db_cgi.js ├── http_loader.js ├── calendar.css ├── holiday.js ├── index.html ├── calendar_db_cookie.js └── calendar.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /default.css: -------------------------------------------------------------------------------- 1 | 2 | h1 { 3 | font-size: 10pt; 4 | } 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js encoding=utf-8 2 | *.html encoding=utf-8 3 | -------------------------------------------------------------------------------- /changes.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/js_calendar/master/changes.txt -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/js_calendar/master/screenshot.png -------------------------------------------------------------------------------- /release/js_calendar_1_0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/js_calendar/master/release/js_calendar_1_0.tar.gz -------------------------------------------------------------------------------- /release/js_calendar_1_1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/misohena/js_calendar/master/release/js_calendar_1_1.tar.gz -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | RELEASE_NAME = js_calendar_1_1 3 | 4 | .PHONY: release 5 | release: 6 | mkdir -p $(RELEASE_NAME) 7 | cp *.css *.js *.html *.txt *.png -R cgi_pstore $(RELEASE_NAME) 8 | tar cvzf release/$(RELEASE_NAME).tar.gz $(RELEASE_NAME) 9 | rm -R $(RELEASE_NAME) 10 | -------------------------------------------------------------------------------- /calendar_db_null.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // 3 | // example of async interface for calendar DB. 4 | // 5 | // Copyright(c) 2009 AKIYAMA Kouhei. 6 | // 7 | function CalendarData() 8 | { 9 | } 10 | CalendarData.prototype = { 11 | readEventItems: function(firstDate, lastDate, callback) 12 | { 13 | if(callback){ 14 | callback([ 15 | // {date:new Date(2009,11-1,15), value:"予定1"}, 16 | // {date:new Date(2009,11-1,25), value:"11:00 予定2"}, 17 | ]); 18 | } 19 | }, 20 | 21 | changeEventItem: function(date, oldValue, newValue, callback) 22 | { 23 | if(callback){ 24 | callback( 25 | true, // succeeded? 26 | newValue // current value 27 | ); 28 | } 29 | }, 30 | }; 31 | 32 | -------------------------------------------------------------------------------- /calendar_null.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | カレンダー 9 | 10 | 11 | 12 | 13 | 14 |

カレンダー

15 | 16 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /calendar_cookie.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | カレンダー(書き込んだ内容はCookieに保存します) 9 | 10 | 11 | 12 | 13 | 14 |

カレンダー(書き込んだ内容はCookieに保存します)

15 | 16 | 19 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /calendar_cgi_pstore.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | カレンダー 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 |

カレンダー

21 | 22 | 25 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /cgi_pstore/list.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # Copyright(c) 2009 AKIYAMA Kouhei. 3 | 4 | # 5 | # synopsis: list.rb?first=YYYYMMDD&last=YYYYMMDD 6 | # (An interval that does not contains lastDate) 7 | # result: [ {date:, value:}, ... ] 8 | # 9 | require 'cgi' 10 | require 'date' 11 | require 'pstore' 12 | 13 | require 'utils' 14 | 15 | cgi = CGI.new 16 | first_date = cgi.has_key?('first') ? str_to_date(cgi['first']) : nil 17 | last_date = cgi.has_key?('last') ? str_to_date(cgi['last']) : nil 18 | 19 | db = PStore.new("calendar.db") 20 | events = nil 21 | db.transaction do 22 | events = db.fetch("events", []) 23 | end 24 | 25 | first_index = first_date ? lower_bound_by_date(events, first_date) : 0 26 | last_index = last_date ? upper_bound_by_date(events, last_date) : events.length 27 | 28 | 29 | print "Content-type: text/plain;charset=utf-8\n\n" 30 | print "[\n" 31 | i = first_index 32 | while i < last_index 33 | if i != first_index 34 | print ",\n" 35 | end 36 | 37 | print "{date:new Date(#{events[i]["date"].year},#{events[i]["date"].month}-1,#{events[i]["date"].mday}), value:#{to_json_string(events[i]["value"])}}" 38 | 39 | i = i + 1 40 | end 41 | print "\n]\n" 42 | -------------------------------------------------------------------------------- /calendar.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | カレンダー 9 | 10 | 11 | 12 | 16 | 19 | 20 | 21 | 22 |

カレンダー

23 | 24 | 27 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /cgi_pstore/utils.rb: -------------------------------------------------------------------------------- 1 | # Copyright(c) 2009 AKIYAMA Kouhei. 2 | 3 | require 'date' 4 | require 'uri' 5 | 6 | def str_to_date(str) 7 | n = str.to_i 8 | return Date.new(n / 10000, n / 100 % 100, n % 100) 9 | end 10 | 11 | def lower_bound_by_date(arr, date) 12 | first = 0 13 | last = arr.length 14 | 15 | while first < last 16 | mid = (first + last) / 2 17 | if arr[mid]["date"] < date 18 | first = mid + 1 19 | else 20 | last = mid 21 | end 22 | end 23 | return first 24 | end 25 | 26 | def upper_bound_by_date(arr, date) 27 | first = 0 28 | last = arr.length 29 | 30 | while first < last 31 | mid = (first + last) / 2 32 | if ! (date < arr[mid]["date"]) 33 | first = mid + 1 34 | else 35 | last = mid 36 | end 37 | end 38 | return first 39 | end 40 | 41 | def find_value(arr, value, low, upp) 42 | index = low 43 | while index < upp 44 | if arr[index]["value"] == value 45 | break 46 | end 47 | index = index + 1 48 | end 49 | return index 50 | end 51 | 52 | def to_json_string(str) 53 | # return "decodeURIComponent(\"" + URI.escape(str, /[^-_.!~*'()a-zA-Z\d?@]/n) + "\")"; 54 | 55 | s = str.gsub(/[^\x20-\x21\x23-\x5b\x5d-\xff]/n) do |ch| 56 | c = ch[0].ord 57 | if c != 0 && (index = "\"\\/\b\f\n\r\t".index(ch[0])) 58 | "\\" + "\"\\/bfnrt"[index, 1] 59 | else 60 | sprintf("\\u%04X", c) 61 | end 62 | end 63 | return '"' + s + '"' 64 | end 65 | -------------------------------------------------------------------------------- /calendar_db_cgi.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // 3 | // async interface for calendar DB CGI. 4 | // 5 | // Copyright(c) 2009 AKIYAMA Kouhei. 6 | 7 | 8 | // require http_loader.js 9 | 10 | function CalendarData() 11 | { 12 | } 13 | CalendarData.CGI_DIR = "." 14 | CalendarData.prototype = { 15 | readEventItems: function(firstDate, lastDate, callback) 16 | { 17 | if(!callback){return;} 18 | 19 | // list.rb?first=YYYYMMDD&last=YYYYMMDD 20 | // (An interval that does not contains lastDate) 21 | // result: [ {date:, value:}, ... ] 22 | var url = CalendarData.CGI_DIR + "/list.rb?" + HttpLoader.encodeKeyValue({ 23 | first: this.toYYYYMMDD(firstDate), 24 | last: this.toYYYYMMDD(lastDate) 25 | }); 26 | HttpLoader.loadJson(url, callback, "GET", null); 27 | }, 28 | 29 | changeEventItem: function(date, oldValue, newValue, callback) 30 | { 31 | // modify.rb?date=YYYYMMDD&old=&new= 32 | // result: {succeeded: , value:} 33 | 34 | var url = CalendarData.CGI_DIR + "/modify.rb"; 35 | var reqstr = HttpLoader.encodeKeyValue({ 36 | date: this.toYYYYMMDD(date), 37 | old:oldValue, 38 | 'new':newValue 39 | }); 40 | HttpLoader.loadJson(url, function(data){ 41 | if(callback){ 42 | if(data){ 43 | callback(data.succeeded, data.value); 44 | } 45 | else{ 46 | callback(false, oldValue); 47 | } 48 | } 49 | }, "POST", reqstr); 50 | }, 51 | 52 | toYYYYMMDD: function(date) 53 | { 54 | return (date.getFullYear()*10000 + (date.getMonth()+1)*100 + date.getDate()).toString(); 55 | } 56 | }; 57 | 58 | -------------------------------------------------------------------------------- /http_loader.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // Copyright(c) 2009 AKIYAMA Kouhei. 3 | 4 | var HttpLoader = {}; 5 | 6 | HttpLoader.createRequestObject = function() 7 | { 8 | try{ 9 | return new XMLHttpRequest(); 10 | } 11 | catch(e){} 12 | try{ 13 | return new ActiveXObject("Msxml2.XMLHTTP"); 14 | } 15 | catch(e){} 16 | 17 | try{ 18 | return new ActiveXObject("Microsoft.XMLHTTP"); 19 | } 20 | catch(e){} 21 | return null; 22 | }; 23 | 24 | HttpLoader.encodeKeyValue = function(obj) 25 | { 26 | var str = ""; 27 | for(var key in obj){ 28 | if(str != ""){ 29 | str += "&"; 30 | } 31 | str += key + "=" + encodeURIComponent(obj[key]); 32 | } 33 | return str; 34 | }; 35 | 36 | HttpLoader.loadText = function(url, func, method, reqstr) 37 | { 38 | var xhr = HttpLoader.createRequestObject(url); 39 | if(!xhr){return null;} 40 | 41 | xhr.onreadystatechange = function(){ 42 | if(xhr.readyState == 4){ //complete 43 | if(xhr.status == 200){ //succeeded 44 | func(xhr.responseText); 45 | } 46 | else{ //failed. 47 | func(null); 48 | } 49 | } 50 | }; 51 | xhr.open(method ? method : "GET", url, true); 52 | if(!reqstr){ reqstr = null;} 53 | xhr.send(reqstr); 54 | }; 55 | 56 | HttpLoader.loadJson = function(url, func, method, reqstr) 57 | { 58 | HttpLoader.loadText(url, function(data){ 59 | if(data){ 60 | var obj; 61 | try{ 62 | obj = eval("(" + data + ")"); 63 | } 64 | catch(e){ 65 | func(null); 66 | return; 67 | } 68 | func(obj); 69 | } 70 | else{ 71 | func(null); 72 | } 73 | }, method, reqstr); 74 | }; 75 | -------------------------------------------------------------------------------- /calendar.css: -------------------------------------------------------------------------------- 1 | 2 | table.calendar-table { 3 | border-collapse: collapse; 4 | border: 1px solid black; 5 | 6 | width: 100%; 7 | } 8 | table.calendar-table > tr > td { 9 | border: 1px solid #808080; 10 | width: 14%; 11 | vertical-align: top; 12 | } 13 | table.calendar-table > tr > th { 14 | border: 1px solid #808080; 15 | width: 14%; 16 | } 17 | 18 | 19 | 20 | .calendar-weekdaynames-row { 21 | color: #ffffff; 22 | background-color: #000000; 23 | } 24 | 25 | .calendar-date-header-row { 26 | } 27 | 28 | .calendar-date-content-row { 29 | height: 4em; 30 | } 31 | 32 | .calendar-date-content-row > td { 33 | padding: 0 0 0.8em 0; /* bottom margin for new event creation */ 34 | } 35 | 36 | .calendar-date-content-row > td > textarea { 37 | margin: 0; 38 | } 39 | 40 | 41 | 42 | .calendar-holiday-header { 43 | background-color: #ffa0a0; 44 | } 45 | 46 | .calendar-holiday-content { 47 | background-color: #ffe0e0; 48 | } 49 | 50 | .calendar-pastday-header { 51 | background-color: #c0c0c0; 52 | } 53 | 54 | .calendar-pastday-content { 55 | background-color: #c0c0c0; 56 | } 57 | 58 | .calendar-normalday-header { 59 | background-color: #d0d8f0; 60 | } 61 | 62 | .calendar-normalday-content { 63 | background-color: #ffffff; 64 | } 65 | 66 | .calendar-month-name { 67 | font-weight: bold; 68 | color: #ffffff; 69 | background-color: #000000; 70 | padding-left: 2px; 71 | padding-right: 2px; 72 | } 73 | 74 | .calendar-event-item { 75 | border: 1px dashed #888888; 76 | } 77 | 78 | /* month separation lines */ 79 | table.calendar-table > tr > td.calendar-last-dayweek-of-month-content { 80 | border-bottom: 3px solid #505050; 81 | } 82 | 83 | table.calendar-table > tr > td.calendar-last-date-of-month-content { 84 | border-right: 3px solid #505050; 85 | } 86 | 87 | table.calendar-table > tr > td.calendar-last-date-of-month-header { 88 | border-right: 3px solid #505050; 89 | } 90 | -------------------------------------------------------------------------------- /cgi_pstore/list_icalendar.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # Copyright(c) 2010 AKIYAMA Kouhei. 3 | 4 | # 5 | # synopsis: list_icalendar.rb?first=YYYYMMDD&last=YYYYMMDD 6 | # (An interval that does not contains lastDate) 7 | # 8 | require 'cgi' 9 | require 'date' 10 | require 'pstore' 11 | 12 | require 'utils' 13 | 14 | CALENDAR_NAME = 'js_calendar' 15 | 16 | cgi = CGI.new 17 | first_date = cgi.has_key?('first') ? str_to_date(cgi['first']) : nil 18 | last_date = cgi.has_key?('last') ? str_to_date(cgi['last']) : nil 19 | 20 | db = PStore.new("calendar.db") 21 | events = nil 22 | db.transaction do 23 | events = db.fetch("events", []) 24 | end 25 | 26 | first_index = first_date ? lower_bound_by_date(events, first_date) : 0 27 | last_index = last_date ? upper_bound_by_date(events, last_date) : events.length 28 | 29 | 30 | #print "Content-type: text/plain;charset=utf-8\n\n" 31 | print "Content-type: text/calendar;charset=utf-8\n\n" 32 | print "BEGIN:VCALENDAR\n"+ 33 | "PRODID:-//AKIYAMA Kouhei//js_calendar//EN\n"+ 34 | "VERSION:2.0\n"+ 35 | "X-WR-CALNAME:"+CALENDAR_NAME+"\n"+ 36 | "X-WR-TIMEZONE:+0900\n"+ 37 | "CALSCALE:GREGORIAN\n" 38 | 39 | i = first_index 40 | while i < last_index 41 | ev = events[i] 42 | value = ev["value"] 43 | date = ev["date"] 44 | dt = date.strftime("%Y%m%d") 45 | 46 | value.gsub!(/\\/, "\\\\\\") 47 | value.gsub!(/\n/, "\\n") 48 | 49 | # date/time 50 | if /^(([0-9]{1,2}):([0-9][0-9]))-(([0-9]{1,2}):([0-9][0-9])|) +(.+)/m =~ value 51 | value = $7 52 | dtstart = "DTSTART:"+dt+"T"+("0"+$2)[-2,2]+$3+"00" 53 | if $5 && $6 54 | dtend = "DTEND:"+dt+"T"+("0"+$5)[-2,2]+$6+"00" 55 | else 56 | dtend = "DTEND:"+dt+"T"+("0"+$2)[-2,2]+$3+"00" 57 | end 58 | else 59 | dtstart = "DTSTART;VALUE=DATE:"+dt 60 | dtend = "DTEND;VALUE=DATE:"+date.next.strftime("%Y%m%d") 61 | end 62 | 63 | # summary 64 | summary = value 65 | 66 | 67 | print "BEGIN:VEVENT\n"+ 68 | dtstart+"\n"+ 69 | dtend+"\n"+ 70 | "SUMMARY:"+summary+"\n"+ 71 | "DESCRIPTION:"+summary+"\n"+ 72 | "END:VEVENT\n" 73 | 74 | i = i + 1 75 | end 76 | 77 | print "END:VCALENDAR\n" 78 | -------------------------------------------------------------------------------- /cgi_pstore/modify.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/ruby 2 | # Copyright(c) 2009 AKIYAMA Kouhei. 3 | 4 | # 5 | # synopsis: modify.rb?date=YYYYMMDD&old=&new= 6 | # result: {succeeded:, value:} 7 | # 8 | 9 | MAX_EVENTS_PER_DAY = 10 10 | MAX_EVENTS_PER_CALENDAR = 365*10 11 | MAX_EVENT_VALUE_BYTES = 256 12 | 13 | require 'cgi' 14 | require 'date' 15 | require 'pstore' 16 | 17 | require 'utils' 18 | 19 | def main() 20 | 21 | cgi = CGI.new 22 | date = cgi.has_key?('date') ? str_to_date(cgi['date']) : nil 23 | old_value = cgi.has_key?('old') ? cgi['old'] : nil 24 | new_value = cgi.has_key?('new') ? cgi['new'] : nil 25 | 26 | 27 | result = { "succeeded"=>false, "curr_value"=>""} 28 | 29 | if date == nil 30 | # error 31 | else 32 | db = PStore.new("calendar.db") 33 | db.transaction do 34 | 35 | # open DB 36 | if !db.root?("events") 37 | db["events"] = [] 38 | end 39 | events = db.fetch("events", []) 40 | 41 | # add/delete/modify event 42 | old_value_valid = !(old_value == "" or old_value == nil) 43 | new_value_valid = !(new_value == "" or new_value == nil) 44 | if (! old_value_valid and new_value_valid) 45 | result = add_event(events, date, new_value) 46 | elsif (old_value_valid and ! new_value_valid) 47 | result = delete_event(events, date, old_value) 48 | elsif (old_value_valid and new_value_valid) 49 | result = modify_event(events, date, old_value, new_value) 50 | end 51 | end 52 | end 53 | 54 | print "Content-type: text/plain;charset=utf-8\n\n" 55 | print "{succeeded:#{result["succeeded"]}, value:#{to_json_string(result["curr_value"])}}\n" 56 | end 57 | 58 | 59 | def add_event(events, date, new_value) 60 | low = lower_bound_by_date(events, date) 61 | upp = upper_bound_by_date(events, date) 62 | 63 | if (upp - low < MAX_EVENTS_PER_DAY) and 64 | (events.length < MAX_EVENTS_PER_CALENDAR) and 65 | (new_value.length <= MAX_EVENT_VALUE_BYTES) 66 | events.insert(upp, {"date"=>date, "value"=>new_value}) 67 | 68 | return { "succeeded"=>true, "curr_value"=>new_value} 69 | else 70 | # error 71 | return { "succeeded"=>false, "curr_value"=>""} 72 | end 73 | end 74 | 75 | def delete_event(events, date, old_value) 76 | low = lower_bound_by_date(events, date) 77 | upp = upper_bound_by_date(events, date) 78 | target = find_value(events, old_value, low, upp) 79 | 80 | if target < upp 81 | events.delete_at(target) 82 | return { "succeeded"=>true, "curr_value"=>""} 83 | else 84 | # error 85 | return { "succeeded"=>false, "curr_value"=>""} 86 | end 87 | end 88 | 89 | def modify_event(events, date, old_value, new_value) 90 | low = lower_bound_by_date(events, date) 91 | upp = upper_bound_by_date(events, date) 92 | target = find_value(events, old_value, low, upp) 93 | 94 | if target < upp 95 | if (new_value.length <= MAX_EVENT_VALUE_BYTES) 96 | events[target]["value"] = new_value 97 | return { "succeeded"=>true, "curr_value"=>new_value} 98 | else 99 | #error 100 | return { "succeeded"=>false, "curr_value"=>events[target]["value"]} 101 | end 102 | else 103 | # error 104 | return { "succeeded"=>false, "curr_value"=>""} 105 | end 106 | end 107 | 108 | 109 | main() 110 | 111 | -------------------------------------------------------------------------------- /holiday.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // 3 | // Copyright(c) 2009 AKIYAMA Kouhei. 4 | /* 5 | 参考文献: 6 | - http://law.e-gov.go.jp/htmldata/S23/S23HO178.html 7 | 国民の祝日に関する法律 8 | (昭和二十三年七月二十日法律第百七十八号) 9 | 最終改正:平成一七年五月二〇日法律第四三号 10 | 11 | - http://law.e-gov.go.jp/htmldata/S41/S41SE376.html 12 | 建国記念の日となる日を定める政令 13 | (昭和四十一年十二月九日政令第三百七十六号) 14 | */ 15 | 16 | var JapaneseHoliday = {}; 17 | 18 | JapaneseHoliday.getNationalHolidayName = function(date) 19 | { 20 | var year = date.getFullYear(); 21 | var month = date.getMonth() + 1; 22 | var day = date.getDate(); 23 | var dayweek = date.getDay(); 24 | var numweek = Math.floor((day - 1) / 7) + 1; 25 | 26 | if(year < 2008){ 27 | return null; // 2008年以降のみ対応 28 | } 29 | 30 | if(month == 1 && day == 1){ 31 | return "元日"; 32 | } 33 | if(month == 1 && dayweek == 1 && numweek == 2){ 34 | return "成人の日"; 35 | } 36 | if(month == 2 && day == 11){ //「建国記念の日となる日を定める政令」による。 37 | return "建国記念の日"; 38 | } 39 | if(month == 3 && day == JapaneseHoliday.getSpringEquinoxDate(year)){ 40 | return "春分の日"; 41 | } 42 | if(month == 4 && day == 29){ 43 | return "昭和の日"; 44 | } 45 | if(month == 5 && day == 3){ 46 | return "憲法記念日"; 47 | } 48 | if(month == 5 && day == 4){ 49 | return "みどりの日"; 50 | } 51 | if(month == 5 && day == 5){ 52 | return "こどもの日"; 53 | } 54 | if(month == 7 && dayweek == 1 && numweek == 3){ 55 | return "海の日"; 56 | } 57 | if(month == 9 && dayweek == 1 && numweek == 3){ 58 | return "敬老の日"; 59 | } 60 | if(month == 9 && day == JapaneseHoliday.getAutumnEquinoxDate(year)){ 61 | return "秋分の日"; 62 | } 63 | if(month == 10 && dayweek == 1 && numweek == 2){ 64 | return "体育の日"; 65 | } 66 | if(month == 11 && day == 3){ 67 | return "文化の日"; 68 | } 69 | if(month == 11 && day == 23){ 70 | return "勤労感謝の日"; 71 | } 72 | if(month == 12 && day == 23){ 73 | return "天皇誕生日"; 74 | } 75 | return null; 76 | }; 77 | 78 | JapaneseHoliday.getHolidayName = function(date) 79 | { 80 | // 3.1 「国民の祝日」は、休日とする。 81 | var nh = JapaneseHoliday.getNationalHolidayName(date); 82 | if(nh){ 83 | return nh; 84 | } 85 | 86 | // 3.2 「国民の祝日」が日曜日に当たるときは、その日後においてその日に最も近い「国民の祝日」でない日を休日とする。 87 | var dw = date.getDay(); 88 | if(dw > 0){ 89 | var prevDate = new Date(date); 90 | for(; dw > 0; --dw){ 91 | prevDate.setTime(prevDate.getTime() - 24*60*60*1000); 92 | if(!JapaneseHoliday.getNationalHolidayName(prevDate)){ 93 | break; 94 | } 95 | } 96 | if(dw == 0){ 97 | return "振替休日"; 98 | } 99 | } 100 | 101 | // 3.3 その前日及び翌日が「国民の祝日」である日(「国民の祝日」でない日に限る。)は、休日とする。 102 | var tomorrow =new Date(date.getFullYear(), date.getMonth(), date.getDate()+1); 103 | var yesterday =new Date(date.getFullYear(), date.getMonth(), date.getDate()-1); 104 | if(JapaneseHoliday.getNationalHolidayName(yesterday) && JapaneseHoliday.getNationalHolidayName(tomorrow)){ 105 | return "国民の休日"; 106 | } 107 | 108 | return null; 109 | }; 110 | 111 | 112 | JapaneseHoliday.getSpringEquinoxDate = function(year) 113 | { 114 | if(year >= 1980 && year < 2100){ 115 | return Math.floor(20.8431 + 0.242194 * (year - 1980)) - Math.floor((year - 1980)/4); 116 | } 117 | else{ 118 | return null; 119 | } 120 | }; 121 | 122 | JapaneseHoliday.getAutumnEquinoxDate = function(year) 123 | { 124 | if(year >= 1980 && year < 2100){ 125 | return Math.floor(23.2488 + 0.242194 * (year - 1980)) - Math.floor((year - 1980)/4); 126 | } 127 | else{ 128 | return null; 129 | } 130 | }; 131 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 大まかな予定を書き込めるJavaScriptカレンダー 7 | 8 | 9 | 10 |

大まかな予定を書き込めるJavaScriptカレンダー

11 |
12 | 13 |
    14 |
  • 紙の卓上カレンダーの代わりに作ったシンプルなJavaScriptカレンダーです。
  • 15 |
  • AJAX的にワンクリックで簡単に予定を書き込めます。
  • 16 |
  • 書き込んだ内容をどう保存するかは、色々選べます。CGIにすれば他の人と共有できます。
  • 17 |
  • 過ぎた日をグレー表示することで今日にフォーカスしやすくします。
  • 18 |
  • 月単位で表を分けないため、月をまたぐ予定でも正確な日数を把握しやすくします。
  • 19 |
20 | 21 |

動作例

22 | 27 | 28 |

ダウンロード

29 | 32 | 33 |

リポジトリ

34 | 37 | 38 |

ファイル構成

39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
分類ファイル名概要依存するファイル
使用例calendar_null.htmlカレンダーのHTML。データを保存しないバージョン。UIのテスト用。default.css, calendar.css, holiday.js, calendar.js, calendar_db_null.js
calendar_cgi_pstore.htmlカレンダーのHTML。CGIを通してデータを保存するバージョン。サーバー上でのデータの保存はRubyのpstoreを使う。default.css, calendar.css, holiday.js, calendar.js, calendar_db_cgi.js, http_loader.js
calendar_cookie.htmlカレンダーのHTML。クッキーにデータを保存するバージョン。サーバーは不要。データの共有は出来ない。容量制限あり。default.css, calendar.css, holiday.js, calendar.js, calendar_db_cookie.js
default.css*.htmlから共通で使用するスタイルシート。
UIcalendar.jsカレンダーのUIを実現するためのJavaScript。テーブルの生成、セルの入力処理、イベント項目の表示・入力処理を行う。実際のデータの読み書きはcalendar_db_*.jsのいずれかに依頼する。holiday.js, calendar_db_*.js
calendar.csscalendar.jsが生成するカレンダーテーブルの見た目を決めるスタイルシート。
holiday.js休日情報を提供するJavaScript。
データインタフェースcalendar_db_null.jsデータを保存しないダミーのカレンダーデータインタフェース。
calendar_db_cgi.jsCGIを通してサーバーにデータを保存するカレンダーデータインタフェース。http_loader.js, cgi_*/list.rb, cgi_*/modify.rb
calendar_db_cookie.jsクッキーにデータを保存するカレンダーデータインタフェース。calendar.jsが使う。
http_loader.jsサーバーとの非同期通信をサポートするJavaScript。calendar_db_cgi.jsが使う。
CGI(pstore)cgi_pstore/list.rb読み込み用CGI。calendar_db_cgi.rbから呼び出す。cgi_pstore/utils.rb
cgi_pstore/modify.rb書き込み用CGI。calendar_db_cgi.rbから呼び出す。cgi_pstore/utils.rb
cgi_pstore/utils.rblist.rbとmodify.rbから使う共通コード。
60 | 61 |

カスタマイズ

62 |

月曜始まりにするには、calendar.js内の「firstDayOfWeek: 0」を「firstDayOfWeek: 1」へ変更してください。

63 |

休みの曜日を変更するには、calendar.js内の「holidayDaysOfWeek:」の後を変更してください。

64 |

見た目(色合いや寸法)を変更するには、calendar.cssを変更してください。

65 | 66 | 67 |

ライセンス

68 |

本ソフトウェアはMITライセンスに基づき自由に使用することが出来ます。

69 | 70 |

Copyright (c) 2009-2010 AKIYAMA Kouhei

71 | 72 |

Permission is hereby granted, free of charge, to any person obtaining a copy 73 | of this software and associated documentation files (the "Software"), to deal 74 | in the Software without restriction, including without limitation the rights 75 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 76 | copies of the Software, and to permit persons to whom the Software is 77 | furnished to do so, subject to the following conditions:

78 | 79 |

The above copyright notice and this permission notice shall be included in 80 | all copies or substantial portions of the Software.

81 | 82 |

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 83 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 84 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 85 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 86 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 87 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 88 | THE SOFTWARE.

89 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /calendar_db_cookie.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // 3 | // クッキーへデータを保存するカレンダーデータベースインタフェースです。 4 | // 5 | // ブラウザがデータを記憶するため、サーバーを用意する必要がありません。 6 | // ただし保存できる容量は3800バイトまでです。その容量を越えた時点でそれ以上書き込めなくなります。 7 | // 7日以上過ぎたイベントは自動的に消去するので、沢山の予定を書き込まなければある程度使い続けることが出来ます。 8 | // ただしクッキーは何かの拍子に誤って消してしまうことがあるので、あまり信用しない方が良いかもしれません。 9 | // 10 | // Copyright(c) 2009 AKIYAMA Kouhei. 11 | // 12 | function CalendarData() 13 | { 14 | } 15 | CalendarData.prototype = { 16 | readEventItems: function(firstDate, lastDate, callback) 17 | { 18 | if(!callback){return;} 19 | 20 | // read all events from the cookie. 21 | var events = this.getEvents(); 22 | 23 | // filter events [firstDate, lastdate) 24 | var firstNum = this.convDateToNum(firstDate); 25 | var lastNum = this.convDateToNum(lastDate); 26 | var eventsInRange = new Array(); 27 | for(var i = 0; i < events.length; ++i){ 28 | var d = this.convDateToNum(events[i].date); 29 | if(d >= firstNum && d < lastNum){ 30 | eventsInRange.push(events[i]); 31 | } 32 | } 33 | 34 | // notify the result. 35 | callback(eventsInRange); 36 | }, 37 | 38 | changeEventItem: function(date, oldValue, newValue, callback) 39 | { 40 | // read all events from the cookie. 41 | var events = this.getEvents(); 42 | 43 | // find the index of the target event(date, oldValue). 44 | var target; 45 | var dateNum = this.convDateToNum(date); 46 | for(target = 0; target < events.length; ++target){ 47 | if(this.convDateToNum(events[target].date) == dateNum && events[target].value == oldValue){ 48 | break; 49 | } 50 | } 51 | 52 | var succeeded = false; 53 | if(! oldValue && newValue){ 54 | // add new event. 55 | events.push({date: new Date(date), value: newValue}); 56 | succeeded = true; 57 | } 58 | else if(oldValue && ! newValue){ 59 | // delete event. 60 | if(target < events.length){ 61 | events.splice(target, 1); 62 | succeeded = true; 63 | } 64 | else{ 65 | //error 66 | } 67 | } 68 | else if(oldValue && newValue){ 69 | // change event value. 70 | if(target < events.length){ 71 | events[target].value = newValue; 72 | succeeded = true; 73 | } 74 | else{ 75 | //error 76 | } 77 | } 78 | 79 | // write all events to the cookie. 80 | if(succeeded){ 81 | succeeded = this.setEvents(events); 82 | } 83 | 84 | // notify the result. 85 | if(callback){ 86 | callback(succeeded, succeeded ? newValue : ""); 87 | } 88 | }, 89 | 90 | convDateToNum: function(date) 91 | { 92 | return date.getFullYear()*10000 + (date.getMonth()+1)*100 + date.getDate(); 93 | }, 94 | 95 | convNumToDate: function(num) 96 | { 97 | return new Date(Math.floor(num / 10000), Math.floor(num/100)%100-1, num%100); 98 | }, 99 | 100 | cookieName: "events", 101 | cookieLengthMax: 3800, 102 | 103 | getEvents: function() 104 | { 105 | var cookies = document.cookie; 106 | //alert(cookies); 107 | var pos = cookies.indexOf(this.cookieName + "="); 108 | if(pos == -1){ 109 | return []; 110 | } 111 | 112 | var first = pos + this.cookieName.length + 1; 113 | var last = cookies.indexOf(";", first); 114 | if(last == -1){ 115 | last = cookies.length; 116 | } 117 | // ex: "events=20091107:birthday&20091107:meeting&20091120:holiday" 118 | var cookieValue = cookies.substring(first, last); 119 | var events = new Array(); 120 | var lines = cookieValue.split('&'); 121 | for(var i = 0; i < lines.length; ++i){ 122 | var datevalue = lines[i].split(':'); 123 | events.push({ 124 | date: this.convNumToDate(parseInt(datevalue[0])), 125 | value: unescape(datevalue[1]) 126 | }); 127 | } 128 | 129 | ///@todo sort 130 | 131 | return events; 132 | }, 133 | 134 | setEvents: function(events) 135 | { 136 | // create a cookie value. and remove old events. 137 | var dateNumLower = this.convDateToNum(new Date((new Date()).getTime() - 7*24*60*60*1000)); 138 | var cookieValue = ""; 139 | for(var i = 0; i < events.length; ++i){ 140 | var dateNum = this.convDateToNum(events[i].date); 141 | if(dateNum < dateNumLower){ 142 | continue; 143 | } 144 | 145 | if(cookieValue != ""){ 146 | cookieValue += "&"; 147 | } 148 | cookieValue += dateNum + ":" + escape(events[i].value) 149 | } 150 | 151 | // validate length of cookie value. 152 | if(cookieValue.length >= this.cookieLengthMax){ 153 | return false; // error 154 | } 155 | 156 | // expiration date of the cookie value. 157 | var expires = new Date(); 158 | expires.setFullYear(expires.getFullYear() + 1); 159 | 160 | // write a cookie. 161 | var cookie = this.cookieName + "=" + cookieValue 162 | + "; expires=" + expires.toGMTString(); 163 | //alert(cookie); 164 | document.cookie = cookie; 165 | 166 | return true; 167 | } 168 | }; 169 | 170 | -------------------------------------------------------------------------------- /calendar.js: -------------------------------------------------------------------------------- 1 | // -*- coding: utf-8 -*- 2 | // title SimpleCalendar 3 | // since 2009-11-03 4 | // author AKIYAMA Kouhei 5 | 6 | // require holiday.js (function ktHolidayName) 7 | // require calendar_db_*.js (function/class CalendarData) 8 | 9 | 10 | 11 | var CalendarApp = { 12 | // Settings. 13 | 14 | cssPrefix: "calendar", 15 | weekDayNames: ["日", "月", "火", "水", "木", "金", "土"], 16 | holidayDaysOfWeek: (1<<0) | (1<<6), 17 | firstDayOfWeek: 0, 18 | displayWeeks: 10, 19 | 20 | // Date/Time Utilities 21 | 22 | getHolidayName: function(date) 23 | { 24 | return JapaneseHoliday.getHolidayName(date); 25 | }, 26 | 27 | isHoliday: function(date) 28 | { 29 | return ((CalendarApp.holidayDaysOfWeek >> date.getDay()) & 1) 30 | || !!CalendarApp.getHolidayName(date); 31 | }, 32 | 33 | isPastDate: function(date, now) 34 | { 35 | if(!now){ 36 | now = new Date(); 37 | } 38 | 39 | if(date.getFullYear() < now.getFullYear()) 40 | return true; 41 | if(date.getFullYear() > now.getFullYear()){ 42 | return false; 43 | } 44 | if(date.getMonth() < now.getMonth()){ 45 | return true; 46 | } 47 | if(date.getMonth() > now.getMonth()){ 48 | return false; 49 | } 50 | if(date.getDate() < now.getDate()){ 51 | return true; 52 | } 53 | //if(date.getDate() > now.getDate()){ 54 | // return false; 55 | //} 56 | return false; 57 | }, 58 | 59 | addDate: function(date, delta) 60 | { 61 | date.setTime(date.getTime() + delta * (24*60*60*1000)); 62 | }, 63 | 64 | convertDateToYYYYMMDD: function(date) 65 | { 66 | return date.getFullYear() * 10000 67 | + (date.getMonth()+1) * 100 68 | + date.getDate(); 69 | }, 70 | 71 | // DOM Utilities 72 | 73 | getLastScriptNode: function() 74 | { 75 | var n = document; 76 | while(n && n.nodeName.toLowerCase() != "script") { n = n.lastChild;} 77 | return n; 78 | }, 79 | /* 80 | createDomNode: function(obj) 81 | { 82 | if(!obj){ 83 | return null; 84 | } 85 | 86 | if(obj.elem){ 87 | var e = document.createElement(obj.elem); 88 | 89 | if(obj.children){ 90 | for(var i = 0; i < obj.children.length; ++i){ 91 | var child = CalendarApp.createDomNode(obj.children[i]); 92 | if(child){ 93 | e.appendChild(child); 94 | } 95 | } 96 | } 97 | 98 | if(obj.atrs){ 99 | for(var key in obj.atrs){ 100 | e[key] = obj.atrs[key]; 101 | } 102 | } 103 | return e; 104 | } 105 | else if(obj.textnode){ 106 | var t = document.createTextNode(obj.textnode); 107 | return t; 108 | } 109 | else{ 110 | return null; 111 | } 112 | }, 113 | */ 114 | 115 | 116 | addEventListener: function(elem, evname, func) 117 | { 118 | if(!elem){return;} 119 | if(elem.addEventListener){ //for DOM 120 | elem.addEventListener(evname, func, false); 121 | } 122 | else if(elem.attachEvent){ //for IE 123 | switch(evname){ 124 | case "click": evname = "onclick"; break; 125 | case "blur": evname = "onblur"; break; 126 | default: evname = ""; break; 127 | } 128 | if(evname != ""){ 129 | elem.attachEvent(evname, func); 130 | } 131 | } 132 | }, 133 | 134 | 135 | // Create Elements. 136 | 137 | createWeekRow: function(firstDate, func, rowClassName) 138 | { 139 | var date = new Date(firstDate); 140 | 141 | var row = document.createElement("tr"); 142 | row.className = rowClassName; 143 | for(var d = 0; d < 7; ++d){ 144 | row.appendChild(func(date)); 145 | CalendarApp.addDate(date, 1); 146 | } 147 | return row; 148 | }, 149 | 150 | createWeekDayNamesRow: function(firstDate) 151 | { 152 | return CalendarApp.createWeekRow( 153 | firstDate, 154 | function(date){ 155 | var th = document.createElement("th"); 156 | th.appendChild(document.createTextNode(CalendarApp.weekDayNames[date.getDay()])); 157 | return th; 158 | }, 159 | CalendarApp.cssPrefix + "-weekdaynames-row"); 160 | }, 161 | 162 | getDateClassName: function(date, now, suffix) 163 | { 164 | var classes = []; 165 | 166 | if(new Date(date.getTime() + 7*24*60*60*1000).getMonth() != date.getMonth()){ 167 | classes.push(CalendarApp.cssPrefix + "-last-dayweek-of-month" + suffix); 168 | } 169 | if(new Date(date.getTime() + 1*24*60*60*1000).getMonth() != date.getMonth()){ 170 | classes.push(CalendarApp.cssPrefix + "-last-date-of-month" + suffix); 171 | } 172 | classes.push(CalendarApp.cssPrefix + ( 173 | CalendarApp.isPastDate(date, now) ? "-pastday" : 174 | CalendarApp.isHoliday(date) ? "-holiday" : 175 | "-normalday") + suffix); 176 | return classes.join(" "); 177 | }, 178 | 179 | createDateHeaderCell: function(date, now) 180 | { 181 | var cell = document.createElement("td"); 182 | cell.className = CalendarApp.getDateClassName(date, now, "-header"); 183 | 184 | if(date.getDate() == 1){ 185 | var monthName = document.createElement("span"); 186 | monthName.className = CalendarApp.cssPrefix + "-month-name"; 187 | monthName.appendChild(document.createTextNode("(" + (1 + date.getMonth()) + ")")); 188 | cell.appendChild(monthName); 189 | cell.appendChild(document.createTextNode("/" + date.getDate())); 190 | } 191 | else{ 192 | cell.appendChild(document.createTextNode(date.getDate())); 193 | } 194 | 195 | var holidayName = CalendarApp.getHolidayName(date); 196 | if(holidayName){ 197 | cell.appendChild(document.createTextNode(":" + holidayName)); 198 | } 199 | return cell; 200 | }, 201 | 202 | createDateContentCell: function(date, now, db, cellsDic) 203 | { 204 | var cell = document.createElement("td"); 205 | cell.className = CalendarApp.getDateClassName(date, now, "-content"); 206 | 207 | var ctrl = new CalendarApp.CalendarCellCtrl(cell, date, db); 208 | if(cellsDic){ 209 | cellsDic[CalendarApp.convertDateToYYYYMMDD(date)] = ctrl; 210 | } 211 | 212 | return cell; 213 | }, 214 | 215 | createDateHeaderRow: function(firstDate, now) 216 | { 217 | return CalendarApp.createWeekRow( 218 | firstDate, 219 | function(date){return CalendarApp.createDateHeaderCell(date, now);}, 220 | CalendarApp.cssPrefix + "-date-header-row"); 221 | }, 222 | 223 | createDateContentRow: function(firstDate, now, db, cellsArray) 224 | { 225 | return CalendarApp.createWeekRow( 226 | firstDate, 227 | function(date){return CalendarApp.createDateContentCell(date, now, db, cellsArray);}, 228 | CalendarApp.cssPrefix + "-date-content-row"); 229 | }, 230 | 231 | 232 | readEventItemsToCells: function(db, firstDate, lastDate, cellsDic) 233 | { 234 | db.readEventItems(firstDate, lastDate, function(items){ 235 | if(!items){ 236 | alert("ERROR:failed to read calendar events."); 237 | return; 238 | } 239 | for(var i = 0; i < items.length; ++i){ 240 | var cell = cellsDic[CalendarApp.convertDateToYYYYMMDD(items[i].date)]; 241 | if(cell){ 242 | cell.addEventItem(items[i].value, false); 243 | } 244 | } 245 | }); 246 | }, 247 | 248 | appendWeeks: function(firstDate, weekCount, db, table, cellsDic, now) 249 | { 250 | var date = new Date(firstDate); 251 | for(var week = 0; week < weekCount; ++week){ 252 | table.appendChild(CalendarApp.createDateHeaderRow(date, now)); 253 | table.appendChild(CalendarApp.createDateContentRow(date, now, db, cellsDic)); 254 | CalendarApp.addDate(date, 7); 255 | } 256 | 257 | CalendarApp.readEventItemsToCells(db, firstDate, date, cellsDic); 258 | 259 | return date; 260 | }, 261 | 262 | createCalendarCtrl: function() 263 | { 264 | var db = new CalendarData(); 265 | 266 | var now = new Date(); 267 | var nowPosOnWeek = (7 + now.getDay() - CalendarApp.firstDayOfWeek) % 7; 268 | 269 | var firstDate = new Date( 270 | now.getFullYear(), 271 | now.getMonth(), 272 | now.getDate() - (nowPosOnWeek + 7)); ///@todo ok? 273 | 274 | 275 | var table = document.createElement("table"); 276 | table.className = CalendarApp.cssPrefix + "-table"; 277 | 278 | table.appendChild(CalendarApp.createWeekDayNamesRow(firstDate)); 279 | 280 | var cellsDic = new Object; 281 | var lastDate = CalendarApp.appendWeeks(firstDate, CalendarApp.displayWeeks, db, table, cellsDic, now); 282 | 283 | return new CalendarApp.CalendarCtrl(db, table, cellsDic, firstDate, lastDate, now); 284 | }, 285 | 286 | placeCalendar: function() 287 | { 288 | var parent = CalendarApp.getLastScriptNode().parentNode; 289 | parent.appendChild(CalendarApp.createCalendarCtrl().div); 290 | } 291 | 292 | }; 293 | 294 | 295 | 296 | // 297 | // calendar control. 298 | // 299 | CalendarApp.CalendarCtrl = function(db, table, cellsDic, firstDate, lastDate, now) 300 | { 301 | this.db = db; 302 | this.table = table; 303 | this.cellsDic = cellsDic; 304 | this.firstDate = firstDate; 305 | this.lastDate = lastDate; 306 | this.now = now; 307 | 308 | this.div = document.createElement("div"); 309 | this.div.appendChild(table); 310 | 311 | this.div.appendChild(this.createButtonMore()); 312 | }; 313 | CalendarApp.CalendarCtrl.prototype = { 314 | createButtonMore: function() 315 | { 316 | var self = this; 317 | var more = document.createElement("input"); 318 | more.setAttribute("type", "button"); 319 | more.setAttribute("value", "more.."); 320 | CalendarApp.addEventListener(more, "click", function(){self.onClickMore();}); 321 | return more; 322 | }, 323 | 324 | onClickMore: function() 325 | { 326 | this.lastDate = CalendarApp.appendWeeks( 327 | this.lastDate, 4, this.db, this.table, this.cellsDic, this.now); 328 | } 329 | }; 330 | 331 | 332 | 333 | // 334 | // calendar cell control. 335 | // 336 | CalendarApp.CalendarCellCtrl = function(cell, date, db) 337 | { 338 | var self = this; 339 | this.date = new Date(date); 340 | this.db = db; 341 | this.cell = cell; 342 | CalendarApp.addEventListener(this.cell, "click", function(){ self.onCellClick();}); 343 | this.items = new Array(); 344 | }; 345 | CalendarApp.CalendarCellCtrl.prototype = { 346 | onCellClick: function() 347 | { 348 | for(var i = 0; i < this.items.length; ++i){ 349 | if(this.items[i].isEditing()){ 350 | return; 351 | } 352 | } 353 | 354 | this.addEventItem("", true); 355 | }, 356 | 357 | addEventItem: function(value, editingMode) 358 | { 359 | ///@todo isEmptyな項目を削除する。 360 | ///@todo 個数制限を設ける。 361 | 362 | this.items.push(new CalendarApp.CalendarEventItemCtrl( 363 | this.cell, value, editingMode, this.date, this.db)); 364 | } 365 | }; 366 | 367 | 368 | 369 | 370 | // 371 | // event item control. 372 | // show/edit a event description. 373 | // 374 | CalendarApp.CalendarEventItemCtrl = function(cell, value, editingMode, date, db) 375 | { 376 | this.date = date; 377 | this.db = db; 378 | this.cell = cell; 379 | this.div = null; 380 | this.textarea = null; 381 | this.msg = null; 382 | this.value = value; 383 | 384 | if(editingMode){ 385 | this.textarea = this.createTextArea(value); 386 | this.cell.appendChild(this.textarea); 387 | this.textarea.focus(); 388 | } 389 | else{ 390 | this.div = this.createDiv(value); 391 | this.cell.appendChild(this.div); 392 | } 393 | }; 394 | CalendarApp.CalendarEventItemCtrl.prototype = { 395 | isEmpty: function() { return !this.textarea && !this.div && !this.msg;}, 396 | isEditing: function() { return !!this.textarea; }, 397 | //isProcessing: function() { return !!this.msg; }, 398 | 399 | createDiv: function(value) 400 | { 401 | var self = this; 402 | var div = document.createElement("div"); 403 | div.className = CalendarApp.cssPrefix + "-event-item"; 404 | div.appendChild(document.createTextNode(value)); 405 | CalendarApp.addEventListener(div, "click", function(){self.onDivClick();}); 406 | return div; 407 | }, 408 | 409 | onDivClick: function() 410 | { 411 | if(!this.div){return;} 412 | 413 | this.textarea = this.createTextArea(this.value); 414 | this.cell.insertBefore(this.textarea, this.div); 415 | this.textarea.focus(); 416 | 417 | this.cell.removeChild(this.div); 418 | this.div = null; 419 | }, 420 | 421 | createTextArea: function(value) 422 | { 423 | var self = this; 424 | var textarea = document.createElement("textarea"); 425 | textarea.className = CalendarApp.cssPrefix + "-event-item-input"; 426 | textarea.value = value; 427 | CalendarApp.addEventListener(textarea, "blur", function(){self.onTextAreaBlur();}); 428 | return textarea; 429 | }, 430 | 431 | onTextAreaBlur: function() 432 | { 433 | if(!this.textarea){return;} 434 | 435 | var self = this; 436 | var oldValue = this.value; 437 | var newValue = this.textarea.value; 438 | ///@todo 値の検証が必要。 439 | if(newValue != oldValue){ 440 | this.value = newValue; 441 | 442 | this.msg = this.createMessageDiv("writing..."); 443 | this.cell.insertBefore(this.msg, this.textarea); 444 | this.db.changeEventItem( 445 | this.date, oldValue, newValue, 446 | function(succeeded, currValue){self.onCalendarDataChanged(succeeded, currValue);}); 447 | } 448 | else{ 449 | if(newValue == ""){ 450 | // cancel to create a new item. 451 | } 452 | else{ 453 | this.div = this.createDiv(this.value); 454 | this.cell.insertBefore(this.div, this.textarea); 455 | } 456 | } 457 | this.cell.removeChild(this.textarea); 458 | this.textarea = null; 459 | }, 460 | 461 | createMessageDiv: function(value) 462 | { 463 | var msg = document.createElement("div"); 464 | msg.className = CalendarApp.cssPrefix + "-event-item-msg"; 465 | msg.appendChild(document.createTextNode(value)); 466 | return msg; 467 | }, 468 | 469 | onCalendarDataChanged: function(succeeded, currValue) 470 | { 471 | if(!this.msg){return;} 472 | 473 | if(!succeeded){ 474 | alert("ERROR:failed to modify the event."); 475 | } 476 | 477 | this.value = currValue; 478 | if(this.value == ""){ 479 | // remove this item. 480 | } 481 | else{ 482 | this.div = this.createDiv(this.value); 483 | this.cell.insertBefore(this.div, this.msg); 484 | } 485 | 486 | this.cell.removeChild(this.msg); 487 | this.msg = null; 488 | } 489 | 490 | }; 491 | 492 | 493 | 494 | --------------------------------------------------------------------------------