├── .gitignore ├── Calendar.js ├── LICENSE ├── README.md ├── bower.json ├── calendar.png ├── index.html ├── package.json └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | /bower_components/ 2 | Thumbs.db 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /Calendar.js: -------------------------------------------------------------------------------- 1 | /*global require, module, window*/ 2 | var Calendar = function (properties) { 3 | 'use strict'; 4 | properties = properties || {}; 5 | var calendar = {}, 6 | m = window.m || require("mithril"), 7 | actual = new Date(), 8 | functionType = Object.prototype.toString.call(function () {}); 9 | 10 | properties.time = properties.time === undefined ? true : properties.time; 11 | 12 | calendar.actual = m.prop(new Date(actual.getFullYear(), actual.getMonth(), actual.getDate())); 13 | 14 | calendar.now = properties.now ? m.prop(properties.now) : m.prop(new Date()); 15 | 16 | calendar.mindate = properties.mindate; 17 | if (calendar.mindate) { 18 | calendar.mindate_nt = new Date(calendar.mindate.getFullYear(), calendar.mindate.getMonth(), calendar.mindate.getDate()); 19 | } 20 | calendar.maxdate = properties.maxdate; 21 | if (calendar.maxdate) { 22 | calendar.maxdate_nt = new Date(calendar.maxdate.getFullYear(), calendar.maxdate.getMonth(), calendar.maxdate.getDate()); 23 | } 24 | 25 | if (calendar.mindate && +calendar.now() < +calendar.mindate) { 26 | calendar.now(calendar.mindate); 27 | } 28 | if (calendar.maxdate && +calendar.now() > +calendar.maxdate) { 29 | calendar.now(calendar.maxdate); 30 | } 31 | 32 | function daysInMonth(month, year) { 33 | return new Date(year, month, 0).getDate(); 34 | } 35 | 36 | function merge(obj1, obj2) { 37 | for (var p in obj2) { 38 | try { 39 | // Property in destination object set; update its value. 40 | if (obj2[p].constructor == Object) { 41 | obj1[p] = merge(obj1[p], obj2[p]); 42 | 43 | } else { 44 | obj1[p] = obj2[p]; 45 | 46 | } 47 | 48 | } catch (e) { 49 | // Property in destination object not set; create it and set its value. 50 | obj1[p] = obj2[p]; 51 | 52 | } 53 | } 54 | return obj1; 55 | } 56 | 57 | calendar.editYear = function(date) { 58 | var cal = this, 59 | year = date().getFullYear(); 60 | return m('span', [ 61 | m('span', { 62 | onclick: function () { 63 | calendar.editingYear(true); 64 | }, 65 | style: calendar.editingYear() ? 'display:none;' : 'cursor: pointer;text-decoration: underline;' 66 | }, year), 67 | m('input', { 68 | value: year, 69 | maxlength: 4, 70 | type: 'text', 71 | required: 'required', 72 | style: !calendar.editingYear() ? 'display:none;' : 'width: 40px;padding: 1px;', 73 | config: function (element) { 74 | if (calendar.editingYear()) { 75 | element.focus(); 76 | element.selectionStart = element.value.length; 77 | } 78 | }, 79 | onblur: function () { 80 | if (+this.value < 1800 || +this.value > 2999) { 81 | return; 82 | } 83 | if (cal.mindate && this.value < cal.mindate.getFullYear()) { 84 | return; 85 | } 86 | if (cal.maxdate && this.value > cal.maxdate.getFullYear()) { 87 | return; 88 | } 89 | cal.date().setFullYear(this.value); 90 | cal.editingYear(false); 91 | } 92 | }), 93 | ]); 94 | }; 95 | 96 | calendar.editingYear = m.prop(false); 97 | 98 | calendar.i18n = { 99 | monthsLong: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], 100 | months: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], 101 | daysLong: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], 102 | days: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'] 103 | }; 104 | 105 | calendar.small = properties.small; 106 | 107 | calendar.i18n = merge(calendar.i18n, properties.i18n); 108 | calendar.formatCell = properties.formatCell || function (date) { 109 | var style = { 110 | cursor: 'pointer' 111 | } 112 | var claz = '', 113 | cal = this; 114 | if (+date === +this.actual()) { 115 | style['background-color'] = '#00b5ad'; 116 | style.color = '#fff'; 117 | } 118 | if (+date === +this.value()) { 119 | style['background-color'] = '#5bbd72'; 120 | style.color = '#fff'; 121 | } 122 | if ((cal.mindate_nt && date < cal.mindate_nt) || (cal.maxdate_nt && date > cal.maxdate_nt)) { 123 | claz = '.disabled'; 124 | } 125 | return m('td.center.aligned' + claz, { 126 | style: style, 127 | onclick: function (e) { 128 | e.preventDefault(); 129 | cal.value(date); 130 | if (properties.onclick) { 131 | properties.onclick(cal.getDate()); 132 | } 133 | } 134 | }, [ 135 | claz.indexOf('.disabled') < 0 ? 136 | m('a[href="#"]', { 137 | style: style, 138 | onclick: function (e) { 139 | e.preventDefault(); 140 | } 141 | }, date.getDate()) : 142 | date.getDate() 143 | ]); 144 | }; 145 | 146 | calendar.goToDate = function (date) { 147 | if (Object.prototype.toString.call(date) === functionType) { 148 | date = date.date(); 149 | } 150 | calendar.now(date); 151 | if (calendar.mindate && +date < +calendar.mindate) { 152 | calendar.now(calendar.mindate); 153 | } 154 | if (calendar.maxdate && +date > +calendar.maxdate) { 155 | calendar.now(calendar.maxdate); 156 | } 157 | calendar.now = m.prop(new Date(calendar.now().getFullYear(), calendar.now().getMonth(), calendar.now().getDate())); 158 | calendar.date = m.prop(new Date(calendar.now().getFullYear(), calendar.now().getMonth(), 1)); 159 | calendar.hours = m.prop(date.getHours()); 160 | calendar.minutes = m.prop(date.getMinutes()); 161 | calendar.value = m.prop(calendar.now()); 162 | }; 163 | 164 | calendar.getDate = function () { 165 | return new Date(this.value().getFullYear(), this.value().getMonth(), this.value().getDate(), this.hours(), this.minutes()); 166 | }; 167 | 168 | calendar.hours = properties.time ? m.prop(calendar.now().getHours()) : m.prop(0); 169 | calendar.minutes = properties.time ? m.prop(calendar.now().getMinutes()) : m.prop(0); 170 | calendar.now = m.prop(new Date(calendar.now().getFullYear(), calendar.now().getMonth(), calendar.now().getDate())); 171 | calendar.date = m.prop(new Date(calendar.now().getFullYear(), calendar.now().getMonth(), 1)); 172 | calendar.goToDate(properties.value || calendar.now());//m.prop(calendar.now()); 173 | 174 | calendar.setMaxDate = function (date) { 175 | m.startComputation(); 176 | calendar.maxdate = date || null; 177 | if (calendar.maxdate) { 178 | calendar.maxdate_nt = new Date(calendar.maxdate.getFullYear(), calendar.maxdate.getMonth(), calendar.maxdate.getDate()); 179 | } 180 | if (calendar.maxdate && +calendar.now() > +calendar.maxdate) { 181 | calendar.now(calendar.maxdate); 182 | } 183 | m.endComputation(); 184 | }; 185 | 186 | calendar.setMinDate = function (date) { 187 | m.startComputation(); 188 | calendar.mindate = date || null; 189 | if (date) { 190 | calendar.mindate_nt = new Date(calendar.mindate.getFullYear(), calendar.mindate.getMonth(), calendar.mindate.getDate()); 191 | } 192 | if (calendar.mindate && +calendar.now() < +calendar.mindate) { 193 | calendar.now(calendar.mindate); 194 | } 195 | m.endComputation(); 196 | }; 197 | 198 | calendar.view = function () { 199 | var date, 200 | dates, 201 | out = [], 202 | all = [], 203 | cal = this, 204 | next = true, 205 | year, 206 | previous = true; 207 | //add dates 208 | //create data view 209 | date = new Date(cal.date().getFullYear(), cal.date().getMonth(), 1); 210 | year = date.getFullYear(); 211 | 212 | if (calendar.mindate && (cal.date().getFullYear() <= cal.mindate_nt.getFullYear())) { 213 | year = cal.mindate.getFullYear(); 214 | cal.date().setFullYear(year); 215 | if (cal.date().getMonth() - 1 < cal.mindate.getMonth()) { 216 | date.setMonth(cal.mindate.getMonth()); 217 | previous = false; 218 | } 219 | if (cal.date().getMonth() < cal.mindate.getMonth()) { 220 | cal.date().setMonth(cal.mindate.getMonth()); 221 | } 222 | } 223 | 224 | if (calendar.maxdate && (cal.date().getFullYear() >= cal.maxdate_nt.getFullYear())) { 225 | year = cal.maxdate.getFullYear(); 226 | cal.date().setFullYear(year); 227 | if (cal.date().getMonth() + 1 > cal.maxdate.getMonth()) { 228 | date.setMonth(cal.maxdate.getMonth()); 229 | cal.date().setMonth(cal.maxdate.getMonth()); 230 | next = false; 231 | } 232 | } 233 | 234 | date.setFullYear(year); 235 | date.setDate(date.getDate() - date.getDay()); 236 | for (var i = 1; i < 43; i += 1) { 237 | var d = date.getDate(); 238 | out.push(new Date(date)); 239 | date.setDate(d + 1); 240 | if (i % 7 === 0) { 241 | all.push(out); 242 | out = []; 243 | } 244 | } 245 | dates = m.prop(all); 246 | 247 | return m('.ui.row.four.column.sm-calendar' + (cal.small ? '.sm-calendar-small' : ''), { 248 | config: function (el, init) { 249 | if (!init) { 250 | if (el.parentNode.className.indexOf('grid') < 0) { 251 | el.className += " grid"; 252 | if (properties.pageclass) { 253 | el.className += ' page'; 254 | } 255 | el.className = el.className.replace('row',''); 256 | } 257 | } 258 | } 259 | }, [ 260 | m('.column', { 261 | style: 'padding-bottom: 0;' 262 | }, [ 263 | previous ? m('a[href=#].sm-calendar-arrow', { 264 | onclick: function (e) { 265 | e.preventDefault(); 266 | cal.date().setDate(cal.date().getDate() - daysInMonth(cal.date().getMonth(), cal.date().getFullYear())); 267 | } 268 | }, [ 269 | m("i.angle.double.left.icon.sm-calendar-arrow"), 270 | !cal.small ? m('span', cal.i18n.monthsLong[cal.date().getMonth() - 1 < 0 ? cal.i18n.months.length - 1 : cal.date().getMonth() - 1]) : '' 271 | ]) : '' 272 | ]), 273 | m('.column.center.aligned.eight.wide', { 274 | style: 'padding-bottom: 0;' 275 | }, [ 276 | m('select', { 277 | style: 'border: 0;background: transparent;padding: 0 3px;cursor: pointer;-webkit-appearance: none;-moz-appearance: none;appearance: none;text-decoration: underline;display: inline;width: auto;', 278 | value: cal.date().getMonth(), 279 | config: function (el) { 280 | el.value = cal.date().getMonth(); 281 | }, 282 | onchange: function () { 283 | cal.date().setMonth(this.value); 284 | } 285 | }, cal.i18n.months.map(function (item, idx) { 286 | if (cal.mindate && (+cal.date().getFullYear() <= +cal.mindate_nt.getFullYear()) && idx < cal.mindate.getMonth()) { 287 | return ''; 288 | } 289 | if (cal.maxdate && (+cal.date().getFullYear() >= +cal.maxdate_nt.getFullYear()) && idx > cal.maxdate.getMonth()) { 290 | return ''; 291 | } 292 | return m('option[value=' + idx + ']', !cal.small ? cal.i18n.monthsLong[idx] : cal.i18n.months[idx]); 293 | })), 294 | calendar.editYear (cal.date) 295 | ]), 296 | m('.column.right.aligned', { 297 | style: 'padding-bottom: 0;' 298 | }, [ 299 | next ? m('a[href=#].sm-calendar-arrow', { 300 | onclick: function (e) { 301 | e.preventDefault(); 302 | cal.date().setMonth(cal.date().getMonth() + 1); 303 | } 304 | }, [ 305 | !cal.small ? m('span', cal.i18n.monthsLong[cal.date().getMonth() + 1 >= cal.i18n.months.length ? 0 : cal.date().getMonth() + 1]) : '', 306 | m("i.angle.double.right.icon.sm-calendar-arrow") 307 | ]) : '' 308 | ]), 309 | m('.column.sixteen.wide', [ 310 | m('table.ui.table.striped.celled.unstackable.seven.column.compact.small', [ 311 | m('thead', [ 312 | m('tr', cal.i18n.days.map(function (item) { 313 | return m('th', { 314 | style: cal.small ? 'padding: 0;' : '' 315 | }, item); 316 | })) 317 | ]), 318 | m('tbody', dates().map(function (row) { 319 | return m('tr', row.map(cal.formatCell.bind(cal))); 320 | })) 321 | ]) 322 | ]), 323 | m('.column.center.aligned.sixteen.wide', { 324 | style: 'padding-top: 0;' 325 | }, properties.time ? [ 326 | m('select', { 327 | style: 'border: 0;background: transparent;padding: 0 3px;cursor: pointer;-webkit-appearance: none;-moz-appearance: none;appearance: none;text-decoration: underline;display: inline;width: auto;', 328 | value: cal.hours(), 329 | onchange: m.withAttr('value', cal.hours), 330 | config: function (element) { 331 | cal.hours(element.value); 332 | } 333 | }, Array.apply(null, new Array(24)).map(function (item, idx) { 334 | idx += 1; 335 | if (cal.mindate && (+cal.value() <= +cal.mindate_nt) && idx < cal.mindate.getHours()) { 336 | return; 337 | } 338 | if (cal.maxdate && (+cal.value() >= +cal.maxdate_nt) && idx > cal.maxdate.getHours()) { 339 | return; 340 | } 341 | return m('option[value=' + idx + ']', idx < 10 ? ('0' + idx) : idx); 342 | })), 343 | ':', 344 | m('select', { 345 | style: 'border: 0;background: transparent;padding: 0 3px;cursor: pointer;-webkit-appearance: none;-moz-appearance: none;appearance: none;text-decoration: underline;display: inline;width: auto;', 346 | value: cal.minutes(), 347 | onchange: m.withAttr('value', cal.minutes), 348 | config: function (element) { 349 | cal.minutes(element.value); 350 | } 351 | }, Array.apply(null, new Array(60)).map(function (item, idx) { 352 | if (cal.mindate && (+cal.value() <= +cal.mindate_nt) && idx < cal.mindate.getMinutes()) { 353 | return; 354 | } 355 | if (cal.maxdate && (+cal.value() >= +cal.maxdate_nt) && idx > cal.maxdate.getMinutes()) { 356 | return; 357 | } 358 | return m('option[value=' + idx + ']', idx < 10 ? ('0' + idx) : idx); 359 | })), 360 | m('a[href="#"]', { 361 | style: 'padding: 0 3px;float:right;', 362 | onclick: function (e) { 363 | e.preventDefault(); 364 | cal.goToDate(new Date()); 365 | } 366 | }, 'Today') 367 | ] : [ 368 | m('a[href="#"]', { 369 | style: 'padding: 0 3px;float:right;', 370 | onclick: function (e) { 371 | e.preventDefault(); 372 | cal.goToDate(new Date()); 373 | } 374 | }, 'Today') 375 | ]) 376 | ]); 377 | }; 378 | return calendar; 379 | }; 380 | 381 | if (typeof module !== 'undefined' && module.exports) { 382 | module.exports = Calendar; 383 | } 384 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Ivan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sm-calendar 2 | Mithril semantic-ui calendar widget 3 | 4 | It requires mithril and semantic-ui-table and semantic-ui-grid 5 | 6 | It is expected for mithril to be in global (m variable) or it will attempt to load it with `require('mithril')`, [webpack](http://webpack.github.io/docs/) its recommended 7 | 8 |  9 | 10 | ## Demo 11 | 12 | [Demo](http://pinguxx.github.io/sm-calendar/) 13 | 14 | ```html 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | ``` 34 | 35 | ```JavaScript 36 | (function (m, Calendar) { 37 | var module = {}; 38 | 39 | 40 | module.controller = function () { 41 | module.vm.init(); 42 | }; 43 | 44 | module.vm = {}; 45 | module.vm.init = function (data) { 46 | this.customers = data; 47 | this.rowsperpage = 10; 48 | this.filter = m.prop(''); 49 | this.calendar = new Calendar({ 50 | mindate: new Date(new Date().getTime() + 10*24*60*60*1000), 51 | maxdate: new Date(new Date().getTime() + 30*24*60*60*1000 + 10000000) 52 | }); 53 | this.calendar2 = new Calendar({small: true}); 54 | }; 55 | 56 | 57 | module.view = function (/*ctrl*/) { 58 | return m('', [ 59 | m('.ui.grid.page', [ 60 | m('br'), 61 | m('h1.ui.dividing.header', 'Calendar Widget') 62 | ]), 63 | m('.ui.grid.page', [ 64 | m('h2', 'Basic Calendar'), 65 | module.vm.calendar.view(), 66 | m('button.ui..button.primary', { 67 | onclick: function() { 68 | console.log(module.vm.calendar.getDate()); 69 | } 70 | }, 'get') 71 | ]), 72 | m('.ui.grid.page.stackable', [ 73 | m('h2', 'Small Calendar'), 74 | m('.row', [ 75 | m('.ui.column.five.wide', [ 76 | m('.ui.grid', [ 77 | module.vm.calendar2.view() 78 | ]) 79 | ]) 80 | ]), 81 | m('button.ui.button.primary', { 82 | onclick: function() { 83 | console.log(module.vm.calendar2.getDate()); 84 | } 85 | }, 'get') 86 | ]) 87 | ]); 88 | }; 89 | 90 | m.module(window.document.body, module); 91 | }(m, Calendar)); 92 | 93 | ``` 94 | 95 | ## Attributes 96 | It accepts the following properties, all of them are optional 97 | 98 | * mindate, min date that can be selected 99 | * maxdate, max date that can be selected 100 | * small, boolean, if you want to show a small calendar 101 | * value, date, current selected date 102 | * formatCell, function to format the cell, recieves a date object, 103 | * time, boolean, to display the time 104 | * onclick, function to react when the cell its clicked, doesnt work if formatcell its passed too 105 | * i18n, object map with: 106 | * **monthsLong**, array of string months in a long format `January, February` ... 107 | * **months**, array of string months in a small format `Jan, Feb` ... 108 | * **daysLong**, array of string days in a long format `Monday, Tuesday` ... 109 | * **days**, array of string days in a small format `Mon, Tue` ... 110 | 111 | 112 | ## Functions 113 | Creating a calendar 114 | ```JavaScrit 115 | var calendar = new Calendar({ 116 | mindate: new Calendar(); 117 | }); 118 | ``` 119 | Loading the view calendar 120 | ```JavaScrit 121 | m('div', calendar.view()) 122 | ``` 123 | You can jump to a date with 124 | ```JavaScrit 125 | calendar.goToDate(date); //date must be a Date object 126 | ``` 127 | You can get the seleted date 128 | ```JavaScrit 129 | calendar.getDate(); //returns Date object 130 | ``` 131 | You can set the maxdate and mindate 132 | ```JavaScrit 133 | calendar.setMaxDate(date); 134 | calendar.setMinDate(date); 135 | ``` 136 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sm-calendar", 3 | "version": "0.1.0", 4 | "homepage": "https://github.com/pinguxx/sm-calendar", 5 | "authors": [ 6 | "Ivan Torres