diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..271c2e1 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,52 @@ +{ + "env": { + "browser": true, + "jquery": true + }, + "parserOptions": { + "ecmaVersion": 5, + "sourceType": "script" + }, + "extends": "eslint:all", + "rules": { + "dot-location": "off", + "func-names": "off", + "func-style": "off", + "id-length": "off", + "indent": "off", + "keyword-spacing": "off", + "max-len": "off", + "max-lines": "off", + "max-statements": "off", + "multiline-ternary": "off", + "newline-after-var": "off", + "newline-before-return": "off", + "newline-per-chained-call": "off", + "no-bitwise": "off", + "no-extra-parens": "off", + "no-inline-comments": "off", + "no-invalid-this": "off", + "no-magic-numbers": "off", + "no-nested-ternary": "off", + "max-statements-per-line": "off", + "no-plusplus": "off", + "no-tabs": "off", + "no-ternary": "off", + "no-underscore-dangle": "off", + "no-use-before-define": "off", + "no-var": "off", + "one-var": "off", + "no-console": ["error", { "allow": ["warn", "error"] }], + "object-shorthand": "off", + "operator-linebreak": "off", + "padded-blocks": "off", + "prefer-arrow-callback": "off", + "prefer-template": "off", + "quote-props": ["error", "as-needed"] , + "quotes": ["error", "double", { "avoidEscape": true }], + "require-jsdoc": "off", + "sort-vars": "off", + "space-before-function-paren": "off", + "vars-on-top": "off" + } +} \ No newline at end of file diff --git a/css/monthly.css b/css/monthly.css index fb246bf..7c85f8a 100644 --- a/css/monthly.css +++ b/css/monthly.css @@ -1,7 +1,8 @@ + /* Overall wrapper */ .monthly { background: #F3F3F5; - color:#545454; + color: #545454; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -12,15 +13,14 @@ /* Top bar containing title, navigation, and buttons */ .monthly-header { position: relative; - text-align:center; - padding:10px; + text-align: center; + padding: 0.5em; background: #fff; - height: 40px; + height: 3em; box-sizing: border-box; } /* Center area of top bar containing title and buttons */ .monthly-header-title { - font-size:.8em; text-transform: uppercase; } @@ -31,152 +31,148 @@ border: 1px solid #ccc; color: #545454; text-decoration: none; - font-size: 11px; - height: 25px; - padding: 1px 10px 0 10px; + height: 1.75em; + line-height: 1.65em; + padding: 0 0.65em 0 0.65em; box-sizing: border-box; - transition:background .1s; + transition: background .1s; } /* Add some roundy-ness */ .monthly-header-title a:first-of-type { - border-top-left-radius:15px; - border-bottom-left-radius:15px; + border-top-left-radius: 1em; + border-bottom-left-radius: 1em; } .monthly-header-title a:last-of-type { - border-top-right-radius:15px; - border-bottom-right-radius:15px; + border-top-right-radius: 1em; + border-bottom-right-radius: 1em; } .monthly-header-title a:hover { background: #8b8b8b; - border:1px solid #8b8b8b; - color:#fff; + border: 1px solid #8b8b8b; + color: #fff; } .monthly-header-title a:active { background: #222; - border:1px solid #222; - transition:none; + border: 1px solid #222; + transition: none; } /* current month/yr block */ .monthly-header-title-date, .monthly-header-title-date:hover { background: #eee!important; - border:1px solid #ccc!important; - color:#545454!important; + border: 1px solid #ccc!important; + color: #545454!important; cursor: default; } /* Button to reset to current month */ .monthly-reset { - border-left:0!important; + border-left: 0!important; +} +.monthly-reset::before { + content: '\21BB'; + margin-right: 0.25em; } /* Button to return to month view */ .monthly-cal { - border-right:0!important; + border-right: 0!important; } -/* wrapper for left button to make the clickable area bigger */ -.monthly-prev { +.monthly-cal::before { + content: '\2637'; + margin-right: 0.25em; +} + +/* wrapper for left/right buttons to make the clickable area bigger */ +.monthly-prev, +.monthly-next { position: absolute; - top:0; - left:0; - width:50px; - height:100%; + top: 0; + width: 3em; + height: 100%; opacity: .5; } -.monthly-prev:hover { - opacity: 1; +.monthly-prev { + left: 0; } -/* Left Arrow */ -.monthly-prev:after{ - content:''; - position: absolute; - top:50%; - left:50%; - border-left:2px solid #222; - border-bottom:2px solid #222; - width:5px; - height:5px; - margin:-3px 0 0 -5px; - -webkit-transform:rotate(45deg) ; - -ms-transform:rotate(45deg) ; - transform:rotate(45deg) ; -} -/* wrapper for right button to make the clickable area bigger */ .monthly-next { - position: absolute; - top:0; - right:0; - width:50px; - height:100%; - opacity: .5; + right: 0; } +.monthly-prev:hover, .monthly-next:hover { opacity: 1; } -/* Right Arrow */ -.monthly-next:after{ - content:''; + +/* Arrows */ +.monthly-prev:after, +.monthly-next:after { + content: ''; position: absolute; - top:50%; - left:50%; - border-right:2px solid #222; - border-top:2px solid #222; - width:5px; - height:5px; - margin:-3px 0 0 -5px; - -webkit-transform:rotate(45deg) ; - -ms-transform:rotate(45deg) ; - transform:rotate(45deg) ; + top: 50%; + left: 50%; + border-style: solid; + border-color: #222; + width: 0.6em; + height: 0.6em; + margin: -0.4em 0 0 -0.4em; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} +/* Left Arrow */ +.monthly-prev:after{ + border-width: 0 0 0.2em 0.2em; +} +/* Right Arrow */ +.monthly-next:after { + border-width: 0.2em 0.2em 0 0; } /* Day of the week headings */ .monthly-day-title-wrap { - display:table; - table-layout:fixed; - width:100%; + display: table; + table-layout: fixed; + width: 100%; background: #fff; border-bottom: 1px solid #EBEBEB; } .monthly-day-title-wrap div { width: 14.28%!important; - display:table-cell; - box-sizing:border-box; + display: table-cell; + box-sizing: border-box; position: relative; - font-weight: bold; - text-align:center; + text-align: center; text-transform: uppercase; - font-size:11px; } /* Calendar days wrapper */ .monthly-day-wrap { - display:table; - table-layout:fixed; - width:100%; + display: table; + table-layout: fixed; + width: 100%; overflow: hidden; } .monthly-week { - display:table-row; - width:100%; + display: table-row; + width: 100%; } /* Calendar Days */ .monthly-day, .monthly-day-blank { width: 14.28%!important; - display:table-cell; + display: table-cell; vertical-align: top; - box-sizing:border-box; + box-sizing: border-box; position: relative; - font-weight: bold; - color:inherit; + color: inherit; background: #fff; box-shadow: 0 0 0 1px #EBEBEB; -webkit-transition: .25s; - transition:.25s; - padding:0; + transition: .25s; + padding: 0; text-decoration: none; } /* Trick to make the days' width equal their height */ .monthly-day:before { - content: ""; + content: ''; display: block; padding-top: 100%; float: left; @@ -189,29 +185,29 @@ /* Days that are part of previous or next month */ .monthly-day-blank { - background:#F3F3F5; + background: #F3F3F5; } /* Event calendar day number styles */ .monthly-day-event > .monthly-day-number { position: absolute; line-height: 1em; - top:2px; - left:2px; - font-size:11px; + top: 0.2em; + left: 0.25em; } /* Non-Event calendar day number styles */ -.monthly-day-pick { -} -.monthly-day-pick > .monthly-day-number { - line-height: 1em; - font-size:11px; - padding-top:35%; +.monthly-day-pick > .monthly-indicator-wrap { + margin: 0; } -.monthly-day-pick > .monthly-indicator-wrap { - margin:0; +.monthly-day-pick > .monthly-day-number:before, +.monthly-day-pick > .monthly-day-number:after { + content: ''; + display: block; + padding-top: calc(50% - 0.8em); + width: 0; + height: 0; } /* Days in the past in "picker" mode */ @@ -233,22 +229,25 @@ .monthly-past-day:hover { background: #fff!important; } + /* Current day style */ .monthly-today .monthly-day-number { color: #FFF; background: #EA6565; - border-radius: 20px; - top: 1px; - left: 1px; - font-size: 10px; - width: 18px; - height: 18px; - line-height: 18px; + border-radius: 0.75em; + top: 0.08em; + left: 0.05em; + font-size: 0.9em; + padding: 0; + width: 1.25em; + height: 1.25em; + line-height: 1.25em; text-align: center; } .monthly-day-pick.monthly-today .monthly-day-number { - padding:0; - margin:22% 22% 0 22%; + padding: 0.15em; + margin: calc(50% - 0.7em) auto auto auto; + font-size: 1em; } /* Wrapper around events */ @@ -256,23 +255,24 @@ position: relative; text-align: center; line-height: 0; - max-width: 20px; - margin:0 auto; - padding-top:40%; + max-width: 1.5em; + margin: 0 auto; + padding-top: 1.2em; } /* Event indicator dots */ .monthly-day .monthly-event-indicator { display: inline-block; - margin: 1px; - width: 8px; - height: 8px; - border-radius: 6px; + margin: 0.05em; + width: 0.5em; + height: 0.5em; + border-radius: 0.25em; vertical-align: middle; - border-radius: 6px; - background:#7BA7CE; - font-size:0; - color:transparent; + background: #7BA7CE; +} + +.monthly-day .monthly-event-indicator span { + color: transparent; } .monthly-day .monthly-event-indicator:hover { @@ -283,31 +283,30 @@ background: rgba(233, 235, 236, 0.9); overflow: auto; position: absolute; - top: 42px; + top: 2.5em; width: 100%; - height: calc(100% - 42px); + height: calc(100% - 2.5em); display: none; -webkit-transition: .25s; - transition:.25s; - -webkit-transform:scale(0); - -ms-transform:scale(0); - transform:scale(0); + transition: .25s; + -webkit-transform: scale(0); + -ms-transform: scale(0); + transform: scale(0); } /* Days in Events List */ .monthly-list-item { position: relative; - padding:10px 10px 5px 50px; + padding: 0.5em 0.7em 0.25em 4em; display: none; border-top: 1px solid #D6D6D6; text-align: left; } .monthly-list-item:after{ - content:'No Events'; - padding:4px 10px; - display:block; - margin-bottom:5px; + padding: 0.4em 1em; + display: block; + margin-bottom: 0.5em; } .monthly-event-list .monthly-today .monthly-event-list-date { @@ -317,10 +316,10 @@ /* Events in Events List */ .monthly-event-list .listed-event { display: block; - color:#fff; - padding:4px 10px; - border-radius:2px; - margin-bottom: 5px; + color: #fff; + padding: 0.4em 1em; + border-radius: 0.2em; + margin-bottom: 0.5em; } .monthly-list-item a:link, .monthly-list-item a:visited { @@ -332,83 +331,102 @@ } .item-has-event:after{ - display:none!important; + display: none!important; } .monthly-event-list-date { - width:50px; + width: 4em; position: absolute; - left:0; - top:13px; + left: 0; + top: 1.2em; text-align: center; - font-size: 12px; font-weight: bold; line-height: 1.2em; } .monthly-list-time-start, .monthly-list-time-end { - font-size:.8em; + font-size: .8em; display: inline-block; } .monthly-list-time-end:not(:empty):before { - content:'-'; - padding:0 2px; + content: '\2013'; + padding: 0 2px; } /* Events List custom webkit scrollbar */ - -.monthly-event-list::-webkit-scrollbar {width: 9px;} +.monthly-event-list::-webkit-scrollbar {width: 0.75em;} /* Track */ .monthly-event-list::-webkit-scrollbar-track {background: none;} /* Handle */ .monthly-event-list::-webkit-scrollbar-thumb { - background:#ccc; - border:1px solid #E9EBEC; - border-radius: 10px; -} -.monthly-event-list::-webkit-scrollbar-thumb:hover {background:#555;} - -/* Increase font & spacing over larger size */ -@media (min-width: 400px) { - .monthly-day-number { - top: 5px; - left: 5px; - font-size: 13px; - } + background: #ccc; + border: 1px solid #E9EBEC; + border-radius: 0.5em; } -/* Styles for large mode where text is revealed within events */ -@media (min-width: 600px) { +.monthly-event-list::-webkit-scrollbar-thumb:hover {background: #555;} + +/* Language-specific. Default is English. */ +.monthly-reset:after { content: 'Today'; } +.monthly-cal:after { content: 'Month'; } +.monthly-list-item:after { content: 'No Events'; } + +.monthly-locale-fr .monthly-reset:after { content: "aujourd'hui"; } +.monthly-locale-fr .monthly-cal:after { content: "mois"; } +.monthly-locale-fr .monthly-list-item:after { content: 'aucun événement'; } + + +/* +Calendar shows event titles if the device width allows for at least 3em per day (rounded +up to 25em total). This assumes the calendar font is close to the baseline font size and +the calendar takes up close to the full media width as the window is made smaller or the +font is zoomed. If one or both of these is not true, this will need to be overridden by +a layout-specific width, or you will need to use a library like css-element-queries to +establish the rules based on the calendar element width rather than the device width. +*/ +@media (min-width: 25em) { .monthly-day-event { - padding-top: 20px; - } - .monthly-day-event:before { - padding-top: 77%; + padding-top: 1.3em; } .monthly-day-event > .monthly-indicator-wrap { - width:auto; + width: auto; max-width: none; } .monthly-indicator-wrap { - padding:0; + padding: 0; + } + .monthly-day:before { + padding-top: calc(100% - 1.2em); } .monthly-day .monthly-event-indicator { display: block; margin: 0 0 1px 0; width: auto; - height:20px; - font-size: 10px; - padding: 4px; - border-radius:0; + height: 1.5em; + line-height: 1.2em; + padding: 0.125em 0 0.1em 0.125em; + border-radius: 0; overflow: hidden; - text-overflow: ellipsis; - color:#fff; - text-shadow:0 0 2px rgba(0,0,0,.2); + background-color: #333; + color: #333; text-decoration: none; - line-height: 1em; white-space: nowrap; box-sizing: border-box; } -} + .monthly-day .monthly-event-indicator.monthly-event-continued { + box-shadow: -1px 0 0 0; + } + .monthly-day .monthly-event-indicator span { + display: block; + width: auto; + margin: 0; + color: #fff; + padding: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + +} \ No newline at end of file diff --git a/event-tests.html b/event-tests.html new file mode 100644 index 0000000..07627ea --- /dev/null +++ b/event-tests.html @@ -0,0 +1,135 @@ + + + + + + + Monthly - Event Tests + + + + +

Monthly.js Event Tests

+

October - December 2016

+

This contains events intended to test basic event span functionality of monthly.js

+
+ + + + \ No newline at end of file diff --git a/index.html b/index.html index b7a2b75..6fd394f 100644 --- a/index.html +++ b/index.html @@ -48,6 +48,7 @@ } .monthly { box-shadow: 0 13px 40px rgba(0, 0, 0, 0.5); + font-size: 0.8em; } input[type="text"] { diff --git a/js/monthly.js b/js/monthly.js index 18648be..0395f35 100644 --- a/js/monthly.js +++ b/js/monthly.js @@ -2,424 +2,495 @@ Monthly 2.1.0 by Kevin Thornbloom is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. */ -(function($) { +(function ($) { + "use strict"; $.fn.extend({ - monthly: function(options) { + monthly: function(customOptions) { + // These are overridden by options declared in footer var defaults = { - weekStart: 'Sun', - mode: '', - xmlUrl: '', - jsonUrl: '', - dataType: 'xml', - target: '', + dataType: "xml", + disablePast: false, eventList: true, + events: "", + jsonUrl: "", + linkCalendarToEventUrl: false, maxWidth: false, + mode: "event", setWidth: false, + showTrigger: "", startHidden: false, - showTrigger: '', stylePast: false, - disablePast: false - } - - var options = $.extend(defaults, options), - that = this, - uniqueId = $(this).attr('id'), - d = new Date(), - currentMonth = d.getMonth() + 1, - currentYear = d.getFullYear(), - currentDay = d.getDate(), - monthNames = options.monthNames || ["Jan", "Feb", "Mar", "Apr", "May", "June", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - dayNames = options.dayNames || ['SUN','MON','TUE','WED','THU','FRI','SAT']; - - if (options.maxWidth != false){ - $('#'+uniqueId).css('maxWidth',options.maxWidth); + target: "", + useIsoDateFormat: false, + weekStart: 0, // Sunday + xmlUrl: "" + }; + + var options = $.extend(defaults, customOptions), + uniqueId = $(this).attr("id"), + parent = "#" + uniqueId, + currentDate = new Date(), + currentMonth = currentDate.getMonth() + 1, + currentYear = currentDate.getFullYear(), + currentDay = currentDate.getDate(), + locale = (options.locale || defaultLocale()).toLowerCase(), + monthNameFormat = options.monthNameFormat || "short", + weekdayNameFormat = options.weekdayNameFormat || "short", + monthNames = options.monthNames || defaultMonthNames(), + dayNames = options.dayNames || defaultDayNames(), + markupBlankDay = '
', + weekStartsOnMonday = options.weekStart === "Mon" || options.weekStart === 1 || options.weekStart === "1", + primaryLanguageCode = locale.substring(0, 2).toLowerCase(); + + if (options.maxWidth !== false) { + $(parent).css("maxWidth", options.maxWidth); } - if (options.setWidth != false){ - $('#'+uniqueId).css('width',options.setWidth); + if (options.setWidth !== false) { + $(parent).css("width", options.setWidth); } - if (options.startHidden == true){ - $('#'+uniqueId).addClass('monthly-pop').css({ - 'position' : 'absolute', - 'display' : 'none' + if (options.startHidden) { + $(parent).addClass("monthly-pop").css({ + display: "none", + position: "absolute" }); - $(document).on('focus', ''+options.showTrigger+'', function (e) { - $('#'+uniqueId).show(); - e.preventDefault(); + $(document).on("focus", String(options.showTrigger), function (event) { + $(parent).show(); + event.preventDefault(); }); - $(document).on('click', ''+options.showTrigger+', .monthly-pop', function (e) { - e.stopPropagation(); - e.preventDefault(); + $(document).on("click", String(options.showTrigger) + ", .monthly-pop", function (event) { + event.stopPropagation(); + event.preventDefault(); }); - $(document).on('click', function (e) { - $('#'+uniqueId).hide(); + $(document).on("click", function () { + $(parent).hide(); }); } // Add Day Of Week Titles - if (options.weekStart == 'Sun') { - $('#' + uniqueId).append('
'+dayNames[0]+'
'+dayNames[1]+'
'+dayNames[2]+'
'+dayNames[3]+'
'+dayNames[4]+'
'+dayNames[5]+'
'+dayNames[6]+'
'); - } else if (options.weekStart == 'Mon') { - $('#' + uniqueId).append('
'+dayNames[1]+'
'+dayNames[2]+'
'+dayNames[3]+'
'+dayNames[4]+'
'+dayNames[5]+'
'+dayNames[6]+'
'+dayNames[0]+'
'); - } else { - console.error('Monthly.js has an incorrect entry for the weekStart variable'); - } + _appendDayNames(weekStartsOnMonday); + + // Add CSS classes for the primary language and the locale. This allows for CSS-driven + // overrides of the language-specific header buttons. Lowercased because locale codes + // are case-insensitive but CSS is not. + $(parent).addClass("monthly-locale-" + primaryLanguageCode + " monthly-locale-" + locale); // Add Header & event list markup - $('#' + uniqueId).prepend('
').append('
'); + $(parent).prepend('
').append('
'); + + // Set the calendar the first time + setMonthly(currentMonth, currentYear); // How many days are in this month? - function daysInMonth(m, y){ - return m===2?y&3||!(y%25)&&y&15?28:29:30+(m+(m>>3)&1); + function daysInMonth(month, year) { + return month === 2 ? (year & 3) || (!(year % 25) && year & 15) ? 28 : 29 : 30 + (month + (month >> 3) & 1); } - // Massive function to build the month - function setMonthly(m, y){ - $('#' + uniqueId).data('setMonth', m).data('setYear', y); + // Build the month + function setMonthly(month, year) { + $(parent).data("setMonth", month).data("setYear", year); // Get number of days - var dayQty = daysInMonth(m, y), + var index = 0, + dayQty = daysInMonth(month, year), // Get day of the week the first day is - mZeroed = m -1, - firstDay = new Date(y, mZeroed, 1, 0, 0, 0, 0).getDay(); + mZeroed = month - 1, + firstDay = new Date(year, mZeroed, 1, 0, 0, 0, 0).getDay(), + settingCurrentMonth = month === currentMonth && year === currentYear; // Remove old days - $('#' + uniqueId + ' .monthly-day, #' + uniqueId + ' .monthly-day-blank').remove(); - $('#'+uniqueId+' .monthly-event-list').empty(); - $('#'+uniqueId+' .monthly-day-wrap').empty(); + $(parent + " .monthly-day, " + parent + " .monthly-day-blank").remove(); + $(parent + " .monthly-event-list, " + parent + " .monthly-day-wrap").empty(); // Print out the days - if (options.mode == 'event') { - for(var i = 0; i < dayQty; i++) { - - var day = i + 1; // Fix 0 indexed days - var dayNamenum = new Date(y, mZeroed, day, 0, 0, 0, 0).getDay() - - $('#' + uniqueId + ' .monthly-day-wrap').append('
'+day+'
'); - $('#' + uniqueId + ' .monthly-event-list').append('
'+dayNames[dayNamenum]+'
'+day+'
'); - } - } else { - for(var i = 0; i < dayQty; i++) { - // Fix 0 indexed days - var day = i + 1; - - // Check if it's a day in the past - if(((day < currentDay && m === currentMonth) || y < currentYear || (m < currentMonth && y == currentYear)) && options.stylePast == true){ - $('#' + uniqueId + ' .monthly-day-wrap').append('
'+day+'
'); - } else { - $('#' + uniqueId + ' .monthly-day-wrap').append('
'+day+'
'); - } + for(var dayNumber = 1; dayNumber <= dayQty; dayNumber++) { + // Check if it's a day in the past + var isInPast = options.stylePast && ( + year < currentYear + || (year === currentYear && ( + month < currentMonth + || (month === currentMonth && dayNumber < currentDay) + ))), + innerMarkup = '
' + dayNumber + '
'; + if(options.mode === "event") { + var dayOfWeek = new Date(year, mZeroed, dayNumber, 0, 0, 0, 0).getDay(); + $(parent + " .monthly-day-wrap").append("" + innerMarkup + ""); + $(parent + " .monthly-event-list").append("
' + dayNames[dayOfWeek] + "
" + dayNumber + "
"); + } else { + $(parent + " .monthly-day-wrap").append("" + innerMarkup + ""); } } - - // Set Today - var setMonth = $('#' + uniqueId).data('setMonth'), - setYear = $('#' + uniqueId).data('setYear'); - if (setMonth == currentMonth && setYear == currentYear) { - $('#' + uniqueId + ' *[data-number="'+currentDay+'"]').addClass('monthly-today'); + if (settingCurrentMonth) { + $(parent + ' *[data-number="' + currentDay + '"]').addClass("monthly-today"); } // Reset button - if (setMonth == currentMonth && setYear == currentYear) { - $('#' + uniqueId + ' .monthly-header-title-date').html(monthNames[m - 1] +' '+ y); - } else { - $('#' + uniqueId + ' .monthly-header-title').html(''+monthNames[m - 1] +' '+ y +'↻ TODAY '); - } + $(parent + " .monthly-header-title").html( + '' + monthNames[month - 1] + " " + year + "" + + (settingCurrentMonth ? "" : '')); // Account for empty days at start - if(options.weekStart == 'Sun' && firstDay != 7) { - for(var i = 0; i < firstDay; i++) { - $('#' + uniqueId + ' .monthly-day-wrap').prepend('
'); - } - } else if (options.weekStart == 'Mon' && firstDay == 0) { - for(var i = 0; i < 6; i++) { - $('#' + uniqueId + ' .monthly-day-wrap').prepend('
'); - } - } else if (options.weekStart == 'Mon' && firstDay != 1) { - for(var i = 0; i < (firstDay - 1); i++) { - $('#' + uniqueId + ' .monthly-day-wrap').prepend('
'); + if(weekStartsOnMonday) { + if (firstDay === 0) { + _prependBlankDays(6); + } else if (firstDay !== 1) { + _prependBlankDays(firstDay - 1); } + } else if(firstDay !== 7) { + _prependBlankDays(firstDay); } - //Account for empty days at end - var numdays = $('#' + uniqueId + ' .monthly-day').length, - numempty = $('#' + uniqueId + ' .monthly-day-blank').length, + // Account for empty days at end + var numdays = $(parent + " .monthly-day").length, + numempty = $(parent + " .monthly-day-blank").length, totaldays = numdays + numempty, - roundup = Math.ceil(totaldays/7) * 7, + roundup = Math.ceil(totaldays / 7) * 7, daysdiff = roundup - totaldays; - if(totaldays % 7 != 0) { - for(var i = 0; i < daysdiff; i++) { - $('#' + uniqueId + ' .monthly-day-wrap').append('
'); + if(totaldays % 7 !== 0) { + for(index = 0; index < daysdiff; index++) { + $(parent + " .monthly-day-wrap").append(markupBlankDay); } } // Events - if (options.mode == 'event') { - // Remove previous events - - // Add Events - var addEvents = function(event) { - // Year [0] Month [1] Day [2] - - var fullstartDate = options.dataType == 'xml' ? $(event).find('startdate').text() : event.startdate, - startArr = fullstartDate.split("-"), - startYear = startArr[0], - startMonth = parseInt(startArr[1], 10), - startDay = parseInt(startArr[2], 10), - fullendDate = options.dataType == 'xml' ? $(event).find('enddate').text() : event.enddate, - endArr = fullendDate.split("-"), - endYear = endArr[0], - endMonth = parseInt(endArr[1], 10), - endDay = parseInt(endArr[2], 10), - eventURL = options.dataType == 'xml' ? $(event).find('url').text() : event.url, - eventTitle = options.dataType == 'xml' ? $(event).find('name').text() : event.name, - eventColor = options.dataType == 'xml' ? $(event).find('color').text() : event.color, - eventId = options.dataType == 'xml' ? $(event).find('id').text() : event.id, - startTime = options.dataType == 'xml' ? $(event).find('starttime').text() : event.starttime, - startSplit = startTime.split(":"), - endTime = options.dataType == 'xml' ? $(event).find('endtime').text() : event.endtime, - endSplit = endTime.split(":"), - eventLink = '', - startPeriod = 'AM', - endPeriod = 'PM'; - - /* Convert times to 12 hour & determine AM or PM */ - if(parseInt(startSplit[0]) >= 12) { - var startTime = (startSplit[0] - 12)+':'+startSplit[1]+''; - var startPeriod = 'PM' - } + if (options.mode === "event") { + addEvents(month, year); + } + var divs = $(parent + " .m-d"); + for(index = 0; index < divs.length; index += 7) { + divs.slice(index, index + 7).wrapAll('
'); + } + } - if(parseInt(startTime) == 0) { - var startTime = '12:'+startSplit[1]+''; - } + function addEvent(event, setMonth, setYear) { + // Year [0] Month [1] Day [2] + var fullStartDate = _getEventDetail(event, "startdate"), + fullEndDate = _getEventDetail(event, "enddate"), + startArr = fullStartDate.split("-"), + startYear = parseInt(startArr[0], 10), + startMonth = parseInt(startArr[1], 10), + startDay = parseInt(startArr[2], 10), + startDayNumber = startDay, + endDayNumber = startDay, + showEventTitleOnDay = startDay, + startsThisMonth = startMonth === setMonth && startYear === setYear, + happensThisMonth = startsThisMonth; + + if(fullEndDate) { + // If event has an end date, determine if the range overlaps this month + var endArr = fullEndDate.split("-"), + endYear = parseInt(endArr[0], 10), + endMonth = parseInt(endArr[1], 10), + endDay = parseInt(endArr[2], 10), + startsInPastMonth = startYear < setYear || (startMonth < setMonth && startYear === setYear), + endsThisMonth = endMonth === setMonth && endYear === setYear, + endsInFutureMonth = endYear > setYear || (endMonth > setMonth && endYear === setYear); + if(startsThisMonth || endsThisMonth || (startsInPastMonth && endsInFutureMonth)) { + happensThisMonth = true; + startDayNumber = startsThisMonth ? startDay : 1; + endDayNumber = endsThisMonth ? endDay : daysInMonth(setMonth, setYear); + showEventTitleOnDay = startsThisMonth ? startDayNumber : 1; + } + } + if(!happensThisMonth) { + return; + } - if(parseInt(endSplit[0]) >= 12) { - var endTime = (endSplit[0] - 12)+':'+endSplit[1]+''; - var endPeriod = 'PM' - } - if(parseInt(endTime) == 0) { - var endTime = '12:'+endSplit[1]+''; - } - if (eventURL){ - var eventLink = 'href="'+eventURL+'"'; - } + var startTime = _getEventDetail(event, "starttime"), + timeHtml = "", + eventURL = _getEventDetail(event, "url"), + eventTitle = _getEventDetail(event, "name"), + eventClass = _getEventDetail(event, "class"), + eventColor = _getEventDetail(event, "color"), + eventId = _getEventDetail(event, "id"), + customClass = eventClass ? " " + eventClass : "", + dayStartTag = "" + + (endTime ? '
' + formatTime(endTime) + "
" : "") + + ""; + } - // function to print out list for multi day events - function multidaylist(){ - var timeHtml = ''; - if (startTime){ - var startTimehtml = '
'+startTime+' '+startPeriod+'
'; - var endTimehtml = ''; - if (endTime){ - var endTimehtml = '
'+endTime+' '+endPeriod+'
'; - } - var timeHtml = startTimehtml + endTimehtml + '
'; - } - $('#'+uniqueId+' .monthly-list-item[data-number="'+i+'"]').addClass('item-has-event').append(''+eventTitle+' '+timeHtml+''); - } + if(options.linkCalendarToEventUrl && eventURL) { + dayStartTag = "" + eventTitle + " " + timeHtml + ""; + for(var index = startDayNumber; index <= endDayNumber; index++) { + var doShowTitle = index === showEventTitleOnDay; + // Add to calendar view + $(parent + ' *[data-number="' + index + '"] .monthly-indicator-wrap').append( + markupDayStart + + attr("class", "monthly-event-indicator" + customClass + // Include a class marking if this event continues from the previous day + + (doShowTitle ? "" : " monthly-event-continued") + ) + + ">" + (doShowTitle ? eventTitle : "") + dayEndTags); + // Add to event list + $(parent + ' .monthly-list-item[data-number="' + index + '"]') + .addClass("item-has-event") + .append(markupListEvent); + } + } - // If event is one day & within month - if (!fullendDate && startMonth == setMonth && startYear == setYear) { - // Add Indicators - $('#'+uniqueId+' *[data-number="'+startDay+'"] .monthly-indicator-wrap').append('
'+eventTitle+'
'); - // Print out event list for single day event - var timeHtml = ''; - if (startTime){ - var startTimehtml = '
'+startTime+' '+startPeriod+'
'; - var endTimehtml = ''; - if (endTime){ - var endTimehtml = '
'+endTime+' '+endPeriod+'
'; - } - var timeHtml = startTimehtml + endTimehtml + '
'; - } - $('#'+uniqueId+' .monthly-list-item[data-number="'+startDay+'"]').addClass('item-has-event').append(''+eventTitle+' '+timeHtml+''); - - - // If event is multi day & within month - } else if (startMonth == setMonth && startYear == setYear && endMonth == setMonth && endYear == setYear){ - for(var i = parseInt(startDay); i <= parseInt(endDay); i++) { - // If first day, add title - if (i == parseInt(startDay)) { - $('#'+uniqueId+' *[data-number="'+i+'"] .monthly-indicator-wrap').append('
'+eventTitle+'
'); - } else { - $('#'+uniqueId+' *[data-number="'+i+'"] .monthly-indicator-wrap').append('
'); - } - multidaylist(); - } - - // If event is multi day, starts in prev month, and ends in current month - } else if ((endMonth == setMonth && endYear == setYear) && ((startMonth < setMonth && startYear == setYear) || (startYear < setYear))) { - for(var i = 0; i <= parseInt(endDay); i++) { - // If first day, add title - if (i==1){ - $('#'+uniqueId+' *[data-number="'+i+'"] .monthly-indicator-wrap').append('
'+eventTitle+'
'); - } else { - $('#'+uniqueId+' *[data-number="'+i+'"] .monthly-indicator-wrap').append('
'); - } - multidaylist(); - } - - // If event is multi day, starts in this month, but ends in next - } else if ((startMonth == setMonth && startYear == setYear) && ((endMonth > setMonth && endYear == setYear) || (endYear > setYear))){ - for(var i = parseInt(startDay); i <= dayQty; i++) { - // If first day, add title - if (i == parseInt(startDay)) { - $('#'+uniqueId+' *[data-number="'+i+'"] .monthly-indicator-wrap').append('
'+eventTitle+'
'); - } else { - $('#'+uniqueId+' *[data-number="'+i+'"] .monthly-indicator-wrap').append('
'); - } - multidaylist(); - } - - // If event is multi day, starts in a prev month, ends in a future month - } else if (((startMonth < setMonth && startYear == setYear) || (startYear < setYear)) && ((endMonth > setMonth && endYear == setYear) || (endYear > setYear))){ - for(var i = 0; i <= dayQty; i++) { - // If first day, add title - if (i == 1){ - $('#'+uniqueId+' *[data-number="'+i+'"] .monthly-indicator-wrap').append('
'+eventTitle+'
'); - } else { - $('#'+uniqueId+' *[data-number="'+i+'"] .monthly-indicator-wrap').append('
'); - } - multidaylist(); - } + function addEvents(month, year) { + if(options.events) { + // Prefer local events if provided + addEventsFromString(options.events, month, year); + } else { + var remoteUrl = options.dataType === "xml" ? options.xmlUrl : options.jsonUrl; + if(remoteUrl) { + // Replace variables for month and year to load from dynamic sources + var url = String(remoteUrl).replace("{month}", month).replace("{year}", year); + $.get(url, {now: $.now()}, function(data) { + addEventsFromString(data, month, year); + }, options.dataType).fail(function() { + console.error("Monthly.js failed to import " + remoteUrl + ". Please check for the correct path and " + options.dataType + " syntax."); + }); + } + } + } - } - }; - - var eventsResource = (options.dataType == 'xml' ? options.xmlUrl : options.jsonUrl); - - $.get(''+eventsResource+'', {now: jQuery.now()}, function(d){ - if (options.dataType == 'xml') { - $(d).find('event').each(function(index, event) { - addEvents(event); - }); - } else if (options.dataType == 'json') { - $.each(d.monthly, function(index, event) { - addEvents(event); - }); - } - }, options.dataType).fail(function() { - console.error('Monthly.js failed to import '+eventsResource+'. Please check for the correct path & '+options.dataType+' syntax.'); + function addEventsFromString(events, setMonth, setYear) { + if (options.dataType === "xml") { + $(events).find("event").each(function(index, event) { + addEvent(event, setMonth, setYear); }); + } else if (options.dataType === "json") { + $.each(events.monthly, function(index, event) { + addEvent(event, setMonth, setYear); + }); + } + } + function attr(name, value) { + var parseValue = String(value); + var newValue = ""; + for(var index = 0; index < parseValue.length; index++) { + switch(parseValue[index]) { + case "'": newValue += "'"; break; + case "\"": newValue += """; break; + case "<": newValue += "<"; break; + case ">": newValue += ">"; break; + default: newValue += parseValue[index]; + } } - var divs = $("#"+uniqueId+" .m-d"); - for(var i = 0; i < divs.length; i+=7) { - divs.slice(i, i+7).wrapAll("
"); + return " " + name + "=\"" + newValue + "\""; + } + + function _appendDayNames(startOnMonday) { + var offset = startOnMonday ? 1 : 0, + dayName = "", + dayIndex = 0; + for(dayIndex = 0; dayIndex < 6; dayIndex++) { + dayName += "
" + dayNames[dayIndex + offset] + "
"; } + dayName += "
" + dayNames[startOnMonday ? 0 : 6] + "
"; + $(parent).append('
' + dayName + '
'); } - // Set the calendar the first time - setMonthly(currentMonth, currentYear); + // Detect the user's preferred language + function defaultLocale() { + return navigator.languages && navigator.languages.length ? navigator.languages[0] : navigator.language; + } - // Function to go back to the month view - function viewToggleButton(){ - if($('#'+uniqueId+' .monthly-event-list').is(":visible")) { - $('#'+uniqueId+' .monthly-cal').remove(); - $('#'+uniqueId+' .monthly-header-title').prepend('☷ MONTH'); + // Use the user's locale if possible to obtain a list of short month names, falling back on English + function defaultMonthNames() { + if(typeof Intl === "undefined") { + return ["Jan", "Feb", "Mar", "Apr", "May", "June", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + } + var formatter = new Intl.DateTimeFormat(locale, {month: monthNameFormat}); + var names = []; + for(var monthIndex = 0; monthIndex < 12; monthIndex++) { + var sampleDate = new Date(2017, monthIndex, 1, 0, 0, 0); + names[monthIndex] = formatter.format(sampleDate); } + return names; } - // Advance months - $(document.body).on('click', '#'+uniqueId+' .monthly-next', function (e) { - var setMonth = $('#' + uniqueId).data('setMonth'), - setYear = $('#' + uniqueId).data('setYear'); - if (setMonth == 12) { - var newMonth = 1, - newYear = setYear + 1; - setMonthly(newMonth, newYear); - } else { - var newMonth = setMonth + 1, - newYear = setYear; - setMonthly(newMonth, newYear); + function formatDate(year, month, day) { + if(options.useIsoDateFormat) { + return new Date(year, month - 1, day, 0, 0, 0).toISOString().substring(0, 10); + } + if(typeof Intl === "undefined") { + return month + "/" + day + "/" + year; + } + return new Intl.DateTimeFormat(locale).format(new Date(year, month - 1, day, 0, 0, 0)); + } + + // Use the user's locale if possible to obtain a list of short weekday names, falling back on English + function defaultDayNames() { + if(typeof Intl === "undefined") { + return ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + } + var formatter = new Intl.DateTimeFormat(locale, {weekday: weekdayNameFormat}), + names = [], + dayIndex = 0, + sampleDate = null; + for(dayIndex = 0; dayIndex < 7; dayIndex++) { + // 2017 starts on a Sunday, so use it to capture the locale's weekday names + sampleDate = new Date(2017, 0, dayIndex + 1, 0, 0, 0); + names[dayIndex] = formatter.format(sampleDate); + } + return names; + } + + function _prependBlankDays(count) { + var wrapperEl = $(parent + " .monthly-day-wrap"), + index = 0; + for(index = 0; index < count; index++) { + wrapperEl.prepend(markupBlankDay); + } + } + + function _getEventDetail(event, nodeName) { + return options.dataType === "xml" ? $(event).find(nodeName).text() : event[nodeName]; + } + + // Returns a 12-hour format hour/minute with period. Opportunity for future localization. + function formatTime(value) { + var timeSplit = value.split(":"); + var hour = parseInt(timeSplit[0], 10); + var period = "AM"; + if(hour > 12) { + hour -= 12; + period = "PM"; + } else if(hour === 0) { + hour = 12; } + return hour + ":" + String(timeSplit[1]) + " " + period; + } + + function setNextMonth() { + var setMonth = $(parent).data("setMonth"), + setYear = $(parent).data("setYear"), + newMonth = setMonth === 12 ? 1 : setMonth + 1, + newYear = setMonth === 12 ? setYear + 1 : setYear; + setMonthly(newMonth, newYear); viewToggleButton(); - e.preventDefault(); + } + + function setPreviousMonth() { + var setMonth = $(parent).data("setMonth"), + setYear = $(parent).data("setYear"), + newMonth = setMonth === 1 ? 12 : setMonth - 1, + newYear = setMonth === 1 ? setYear - 1 : setYear; + setMonthly(newMonth, newYear); + viewToggleButton(); + } + + // Function to go back to the month view + function viewToggleButton() { + if($(parent + " .monthly-event-list").is(":visible")) { + $(parent + " .monthly-cal").remove(); + $(parent + " .monthly-header-title").prepend(''); + } + } + + // Advance months + $(document.body).on("click", parent + " .monthly-next", function (event) { + setNextMonth(); + event.preventDefault(); }); // Go back in months - $(document.body).on('click', '#'+uniqueId+' .monthly-prev', function (e) { - var setMonth = $('#' + uniqueId).data('setMonth'), - setYear = $('#' + uniqueId).data('setYear'); - if (setMonth == 1) { - var newMonth = 12, - newYear = setYear - 1; - setMonthly(newMonth, newYear); - } else { - var newMonth = setMonth - 1, - newYear = setYear; - setMonthly(newMonth, newYear); - } - viewToggleButton(); - e.preventDefault(); + $(document.body).on("click", parent + " .monthly-prev", function (event) { + setPreviousMonth(); + event.preventDefault(); }); // Reset Month - $(document.body).on('click', '#'+uniqueId+' .monthly-reset', function (e) { + $(document.body).on("click", parent + " .monthly-reset", function (event) { $(this).remove(); setMonthly(currentMonth, currentYear); viewToggleButton(); - e.preventDefault(); - e.stopPropagation(); + event.preventDefault(); + event.stopPropagation(); }); // Back to month view - $(document.body).on('click', '#'+uniqueId+' .monthly-cal', function (e) { + $(document.body).on("click", parent + " .monthly-cal", function (event) { $(this).remove(); - $('#' + uniqueId+' .monthly-event-list').css('transform','scale(0)'); - setTimeout(function(){ - $('#' + uniqueId+' .monthly-event-list').hide(); - }, 250); - e.preventDefault(); + $(parent + " .monthly-event-list").css("transform", "scale(0)"); + setTimeout(function() { + $(parent + " .monthly-event-list").hide(); + }, 250); + event.preventDefault(); }); // Click A Day - $(document.body).on('click', '#'+uniqueId+' a.monthly-day', function (e) { + $(document.body).on("click touchstart", parent + " .monthly-day", function (event) { // If events, show events list - if(options.mode == 'event' && options.eventList == true) { - var whichDay = $(this).data('number'); - $('#' + uniqueId+' .monthly-event-list').show(); - $('#' + uniqueId+' .monthly-event-list').css('transform'); - $('#' + uniqueId+' .monthly-event-list').css('transform','scale(1)'); - $('#' + uniqueId+' .monthly-list-item[data-number="'+whichDay+'"]').show(); - - var myElement = document.getElementById(uniqueId+'day'+whichDay); - var topPos = myElement.offsetTop; - $('#' + uniqueId+' .monthly-event-list').scrollTop(topPos); + var whichDay = $(this).data("number"); + if(options.mode === "event" && options.eventList) { + var theList = $(parent + " .monthly-event-list"), + myElement = document.getElementById(uniqueId + "day" + whichDay), + topPos = myElement.offsetTop; + theList.show(); + theList.css("transform"); + theList.css("transform", "scale(1)"); + $(parent + ' .monthly-list-item[data-number="' + whichDay + '"]').show(); + theList.scrollTop(topPos); viewToggleButton(); + if(!options.linkCalendarToEventUrl) { + event.preventDefault(); + } // If picker, pick date - } else if (options.mode == 'picker') { - var whichDay = $(this).data('number'), - setMonth = $('#' + uniqueId).data('setMonth'), - setYear = $('#' + uniqueId).data('setYear'); - + } else if (options.mode === "picker") { + var setMonth = $(parent).data("setMonth"), + setYear = $(parent).data("setYear"); // Should days in the past be disabled? - if($(this).hasClass('monthly-past-day') && options.disablePast == true) { + if($(this).hasClass("monthly-past-day") && options.disablePast) { // If so, don't do anything. - e.preventDefault(); + event.preventDefault(); } else { // Otherwise, select the date ... - $(''+options.target+'').val(setMonth+'/'+whichDay+'/'+setYear); + $(String(options.target)).val(formatDate(setYear, setMonth, whichDay)); // ... and then hide the calendar if it started that way - if(options.startHidden == true) { - $('#'+uniqueId).hide(); + if(options.startHidden) { + $(parent).hide(); } } + event.preventDefault(); } - e.preventDefault(); }); // Clicking an event within the list - $(document.body).on('click', '#'+uniqueId+' .listed-event', function (e) { - var href = $(this).attr('href'); + $(document.body).on("click", parent + " .listed-event", function (event) { + var href = $(this).attr("href"); // If there isn't a link, don't go anywhere if(!href) { - e.preventDefault(); + event.preventDefault(); } }); - } + } }); -})(jQuery); +}(jQuery));