├── .gitignore ├── README.md ├── component.json ├── HISTORY.md ├── bower.json ├── dist ├── ember-calendar.min.css ├── ember-calendar.css ├── ember-calendar.min.js ├── ember-calendar.pre.min.js ├── ember-calendar.js └── ember-calendar.pre.js ├── examples ├── ajax │ ├── index.html │ ├── app.js │ └── style.css ├── simple │ ├── index.html │ ├── app.js │ └── style.css ├── convertible │ ├── index.html │ ├── app.js │ └── style.css └── multitype │ ├── index.html │ ├── style.css │ └── app.js └── src ├── calendar.css ├── calendar.html └── calendar.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Calendar component for Ember.js. http://joinspoton.github.io/ember-calendar/ 2 | 3 | --- 4 | 5 | Building: 6 | 7 | $ npm install csso@~1.3.10 ember-handlebars@~1.2.0 shelljs@~0.2.6 uglify-js@~2.4.6 8 | $ node build.js 9 | 10 | --- 11 | 12 | © 2013 [SpotOn](https://spoton.it), shared under the [MIT License](http://www.opensource.org/licenses/MIT). -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-calendar" 3 | , "repo": "joinspoton/ember-calendar" 4 | , "description": "Calendar component for Ember.js." 5 | , "version": "1.0.0" 6 | , "keywords": ["ember", "calendar"] 7 | , "dependencies": { 8 | "component/jquery": ">=1.10.2" 9 | , "component/ember": "~1.0.0" 10 | , "component/moment": "~2.2.1" 11 | } 12 | , "scripts": [ 13 | "dist/ember-calendar.js" 14 | , "dist/ember-calendar.pre.js" 15 | , "dist/ember-calendar.min.js" 16 | , "dist/ember-calendar.pre.min.js" 17 | ] 18 | , "styles": [ 19 | "dist/ember-calendar.css" 20 | , "dist/ember-calendar.min.css" 21 | ] 22 | , "license": "MIT" 23 | } -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | 1.0.0 / 2013-10-11 2 | ================== 3 | 4 | * Add daily view and view switching. 5 | * Add convertible example demonstrating view switching. 6 | * Add distribution scripts with precompiled templates. 7 | * BREAKING: Rename `Ember.Calendar.CalendarView` to `Ember.Calendar.ContainerView`; add new `Ember.Calendar.CalendarView` that wraps (optional) view switching buttons and calendar body. 8 | * BREAKING: Rename weekly view template: `ember-calendar` to `ember-calendar-week`. 9 | * BREAKING: Rename keys related to weekly view in Ember.Calendar.CalendarController: `dayViewClass` to `weekDayViewClass`, `headingDateViewClass` to `weekHeadingDateViewClass`, `dates` to `weekDates`, `days` to `weekDays`. 10 | * Update build script to use node and npm modules only. -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-calendar" 3 | , "description": "Calendar component for Ember.js." 4 | , "version": "1.0.0" 5 | , "keywords": ["ember", "calendar"] 6 | , "homepage": "http://joinspoton.github.io/ember-calendar/" 7 | , "authors": ["SpotOn "] 8 | , "main": [ 9 | "dist/ember-calendar.css" 10 | , "dist/ember-calendar.min.css" 11 | , "dist/ember-calendar.js" 12 | , "dist/ember-calendar.pre.js" 13 | , "dist/ember-calendar.min.js" 14 | , "dist/ember-calendar.pre.min.js" 15 | ] 16 | , "dependencies": { 17 | "jquery": ">=1.10.2" 18 | , "ember": "~1.0.0" 19 | , "moment": "~2.2.1" 20 | } 21 | , "license": "MIT" 22 | , "ignore": [ 23 | "**/.*" 24 | , "bower_components" 25 | , "node_modules" 26 | ] 27 | } -------------------------------------------------------------------------------- /dist/ember-calendar.min.css: -------------------------------------------------------------------------------- 1 | /* ember-calendar 1.0.0 (https://github.com/joinspoton/ember-calendar) | (c) 2013 SpotOn (https://spoton.it) | http://www.opensource.org/licenses/MIT */ 2 | .ember-calendar-head{position:relative}.ember-calendar-head-previous{left:0;position:absolute;text-align:right;top:0}.ember-calendar-head-next{right:0;position:absolute;text-align:left;top:0}.ember-calendar-head-date{display:inline-block;text-align:center;width:100%;vertical-align:top}.ember-calendar-container{overflow-x:hidden;overflow-y:auto}.ember-calendar-body{position:relative}.ember-calendar-head-times{height:100%;left:0;position:absolute;top:0}.ember-calendar-head-time{height:4.16666666666%}.ember-calendar-days{height:100%}.ember-calendar-day{display:inline-block;height:100%;position:relative;text-align:center;width:100%;vertical-align:top}.ember-calendar-event{position:absolute;width:100%}.ember-calendar-head-week-date,.ember-calendar-week-day{width:14.2857142857%} -------------------------------------------------------------------------------- /examples/ajax/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ember-calendar / examples / simple 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ember-calendar / examples / simple 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/convertible/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ember-calendar / examples / simple 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/ajax/app.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Application 3 | /////////////////////////////////////////////////////////////////////////////// 4 | App = Ember.Application.create(); 5 | 6 | App.ApplicationController = Ember.Controller.extend({ 7 | needs: ['calendar'] 8 | }); 9 | 10 | App.ApplicationRoute = Ember.Route.extend({ 11 | setupController: function () { 12 | this.controllerFor('calendar').update(); 13 | } 14 | }); 15 | 16 | App.ApplicationView = Ember.View.extend({ 17 | templateName: 'application' 18 | }); 19 | 20 | 21 | /////////////////////////////////////////////////////////////////////////////// 22 | // Controller 23 | /////////////////////////////////////////////////////////////////////////////// 24 | App.CalendarController = Ember.Calendar.CalendarController.extend({ 25 | content: [] 26 | , update: function () { 27 | if (!this.get('week')) { return; } 28 | 29 | var self = this; 30 | $.getJSON('http://ember-calendar-ajax.herokuapp.com', { week: self.get('week').toDate() }, function (response) { 31 | if (!response) { return; } 32 | self.clear().pushObjects(response).notifyPropertyChange('content'); 33 | }); 34 | }.observes('week') 35 | }); -------------------------------------------------------------------------------- /examples/multitype/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ember-calendar / examples / multitype 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/calendar.css: -------------------------------------------------------------------------------- 1 | /* All of these selectors + properties are required. 2 | You shouldn't have to customize any of them. */ 3 | 4 | .ember-calendar-head { 5 | position: relative; 6 | } 7 | .ember-calendar-head-previous { 8 | left: 0px; 9 | position: absolute; 10 | text-align: right; 11 | top: 0px; 12 | } 13 | .ember-calendar-head-next { 14 | right: 0px; 15 | position: absolute; 16 | text-align: left; 17 | top: 0px; 18 | } 19 | .ember-calendar-head-date { 20 | display: inline-block; 21 | text-align: center; 22 | width: 100%; 23 | vertical-align: top; 24 | } 25 | .ember-calendar-container { 26 | overflow-x: hidden; 27 | overflow-y: auto; 28 | } 29 | .ember-calendar-body { 30 | position: relative; 31 | } 32 | .ember-calendar-head-times { 33 | height: 100%; 34 | left: 0px; 35 | position: absolute; 36 | top: 0px; 37 | } 38 | .ember-calendar-head-time { 39 | height: 4.16666666666%; 40 | } 41 | .ember-calendar-days { 42 | height: 100%; 43 | } 44 | .ember-calendar-day { 45 | display: inline-block; 46 | height: 100%; 47 | position: relative; 48 | text-align: center; 49 | width: 100%; 50 | vertical-align: top; 51 | } 52 | .ember-calendar-event { 53 | position: absolute; 54 | width: 100%; 55 | } 56 | .ember-calendar-head-week-date { 57 | width: 14.2857142857%; 58 | } 59 | .ember-calendar-week-day { 60 | width: 14.2857142857%; 61 | } -------------------------------------------------------------------------------- /examples/simple/app.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Application 3 | /////////////////////////////////////////////////////////////////////////////// 4 | App = Ember.Application.create(); 5 | 6 | App.ApplicationController = Ember.Controller.extend({ 7 | needs: ['calendar'] 8 | }); 9 | 10 | App.ApplicationView = Ember.View.extend({ 11 | templateName: 'application' 12 | }); 13 | 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // Controller 17 | /////////////////////////////////////////////////////////////////////////////// 18 | App.CalendarController = Ember.Calendar.CalendarController.extend({ 19 | content: function () { 20 | var events = []; 21 | var date; 22 | var time; 23 | var duration; 24 | 25 | for (var i = 0; i < 10; i++) { 26 | date = Math.floor(Math.random() * 7); 27 | time = 1000 * 60 * 60 * 8 + 1000 * 60 * 30 * Math.floor(Math.random() * 24); 28 | duration = 1000 * 60 * 30 * (1 + Math.floor(Math.random() * 5)); 29 | 30 | events.push({ 31 | name: 'Event ' + events.length 32 | , start: moment().startOf('day').add('days', date - moment().day()).add('milliseconds', time) 33 | , end: moment().startOf('day').add('days', date - moment().day()).add('milliseconds', time + duration) 34 | }); 35 | } 36 | 37 | return events; 38 | }.property() 39 | }); -------------------------------------------------------------------------------- /dist/ember-calendar.css: -------------------------------------------------------------------------------- 1 | /* ember-calendar 1.0.0 (https://github.com/joinspoton/ember-calendar) | (c) 2013 SpotOn (https://spoton.it) | http://www.opensource.org/licenses/MIT */ 2 | /* All of these selectors + properties are required. 3 | You shouldn't have to customize any of them. */ 4 | 5 | .ember-calendar-head { 6 | position: relative; 7 | } 8 | .ember-calendar-head-previous { 9 | left: 0px; 10 | position: absolute; 11 | text-align: right; 12 | top: 0px; 13 | } 14 | .ember-calendar-head-next { 15 | right: 0px; 16 | position: absolute; 17 | text-align: left; 18 | top: 0px; 19 | } 20 | .ember-calendar-head-date { 21 | display: inline-block; 22 | text-align: center; 23 | width: 100%; 24 | vertical-align: top; 25 | } 26 | .ember-calendar-container { 27 | overflow-x: hidden; 28 | overflow-y: auto; 29 | } 30 | .ember-calendar-body { 31 | position: relative; 32 | } 33 | .ember-calendar-head-times { 34 | height: 100%; 35 | left: 0px; 36 | position: absolute; 37 | top: 0px; 38 | } 39 | .ember-calendar-head-time { 40 | height: 4.16666666666%; 41 | } 42 | .ember-calendar-days { 43 | height: 100%; 44 | } 45 | .ember-calendar-day { 46 | display: inline-block; 47 | height: 100%; 48 | position: relative; 49 | text-align: center; 50 | width: 100%; 51 | vertical-align: top; 52 | } 53 | .ember-calendar-event { 54 | position: absolute; 55 | width: 100%; 56 | } 57 | .ember-calendar-head-week-date { 58 | width: 14.2857142857%; 59 | } 60 | .ember-calendar-week-day { 61 | width: 14.2857142857%; 62 | } -------------------------------------------------------------------------------- /examples/ajax/style.css: -------------------------------------------------------------------------------- 1 | /* The majority of these selectors + properties are required. 2 | However, you can customize them as you wish. */ 3 | 4 | .ember-calendar { 5 | border: 1px solid #ddd; 6 | font-family: 'myriad-pro', 'Myriad Pro', 'Calibri', 'Helvetica', 'Trebuchet MS', 'Verdana', sans-serif; 7 | height: 500px; 8 | width: 800px; 9 | } 10 | .ember-calendar-head { 11 | background: #ddd; 12 | color: #666; 13 | height: 25px; 14 | font-size: 14px; 15 | } 16 | .ember-calendar-container { 17 | height: 475px; 18 | } 19 | .ember-calendar-body { 20 | height: 1000px; 21 | } 22 | .ember-calendar-head-previous { 23 | width: 50px; /* A */ 24 | } 25 | .ember-calendar-head-next { 26 | width: 25px; /* B */ 27 | } 28 | .ember-calendar-head-dates { 29 | margin-left: 50px; /* A */ 30 | margin-right: 25px; /* B */ 31 | padding: 5px 0px; 32 | } 33 | .ember-calendar-head-times { 34 | border-right: 1px solid #f6f6f6; 35 | -moz-box-sizing: border-box; 36 | box-sizing: border-box; 37 | text-align: right; 38 | padding-right: 5px; 39 | width: 50px; /* A */ 40 | } 41 | .ember-calendar-head-time { 42 | color: #999; 43 | font-size: 13px; 44 | } 45 | .ember-calendar-days { 46 | margin-left: 50px; 47 | margin-right: 25px; 48 | } 49 | .ember-calendar-event { 50 | background: #f6f6f6; 51 | border: 1px solid #eee; 52 | -moz-box-sizing: border-box; 53 | box-sizing: border-box; 54 | padding: 5px; 55 | overflow: hidden; 56 | } 57 | .ember-calendar-event-name { 58 | color: #333; 59 | font-size: 14px; 60 | line-height: 16px; 61 | } 62 | .ember-calendar-event-time, .ember-calendar-event-location { 63 | color: #999; 64 | font-size: 12px; 65 | line-height: 14px; 66 | margin-bottom: 2px; 67 | } -------------------------------------------------------------------------------- /examples/simple/style.css: -------------------------------------------------------------------------------- 1 | /* The majority of these selectors + properties are required. 2 | However, you can customize them as you wish. */ 3 | 4 | .ember-calendar { 5 | border: 1px solid #ddd; 6 | font-family: 'myriad-pro', 'Myriad Pro', 'Calibri', 'Helvetica', 'Trebuchet MS', 'Verdana', sans-serif; 7 | height: 500px; 8 | width: 800px; 9 | } 10 | .ember-calendar-head { 11 | background: #ddd; 12 | color: #666; 13 | height: 25px; 14 | font-size: 14px; 15 | } 16 | .ember-calendar-container { 17 | height: 475px; 18 | } 19 | .ember-calendar-body { 20 | height: 1000px; 21 | } 22 | .ember-calendar-head-previous { 23 | width: 50px; /* A */ 24 | } 25 | .ember-calendar-head-next { 26 | width: 25px; /* B */ 27 | } 28 | .ember-calendar-head-dates { 29 | margin-left: 50px; /* A */ 30 | margin-right: 25px; /* B */ 31 | padding: 5px 0px; 32 | } 33 | .ember-calendar-head-times { 34 | border-right: 1px solid #f6f6f6; 35 | -moz-box-sizing: border-box; 36 | box-sizing: border-box; 37 | text-align: right; 38 | padding-right: 5px; 39 | width: 50px; /* A */ 40 | } 41 | .ember-calendar-head-time { 42 | color: #999; 43 | font-size: 13px; 44 | } 45 | .ember-calendar-days { 46 | margin-left: 50px; 47 | margin-right: 25px; 48 | } 49 | .ember-calendar-event { 50 | background: #f6f6f6; 51 | border: 1px solid #eee; 52 | -moz-box-sizing: border-box; 53 | box-sizing: border-box; 54 | padding: 5px; 55 | overflow: hidden; 56 | } 57 | .ember-calendar-event-name { 58 | color: #333; 59 | font-size: 14px; 60 | line-height: 16px; 61 | } 62 | .ember-calendar-event-time, .ember-calendar-event-location { 63 | color: #999; 64 | font-size: 12px; 65 | line-height: 14px; 66 | margin-bottom: 2px; 67 | } -------------------------------------------------------------------------------- /examples/convertible/app.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Application 3 | /////////////////////////////////////////////////////////////////////////////// 4 | App = Ember.Application.create(); 5 | 6 | App.ApplicationController = Ember.Controller.extend({ 7 | needs: ['calendar'] 8 | }); 9 | 10 | App.ApplicationView = Ember.View.extend({ 11 | templateName: 'application' 12 | }); 13 | 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // Controller 17 | /////////////////////////////////////////////////////////////////////////////// 18 | App.CalendarController = Ember.Calendar.CalendarController.extend({ 19 | states: ['day', 'week'] 20 | , initialState: 'day' 21 | , content: function () { 22 | var events = []; 23 | var date; 24 | var time; 25 | var duration; 26 | var i; 27 | 28 | for (i = 0; i < 3; i++) { 29 | time = 1000 * 60 * 60 * 8 + 1000 * 60 * 30 * Math.floor(Math.random() * 24); 30 | duration = 1000 * 60 * 30 * (1 + Math.floor(Math.random() * 5)); 31 | 32 | events.push({ 33 | name: 'Event ' + events.length 34 | , start: moment().startOf('day').add('milliseconds', time) 35 | , end: moment().startOf('day').add('milliseconds', time + duration) 36 | }); 37 | } 38 | 39 | for (i = 0; i < 45; i++) { 40 | date = Math.floor(Math.random() * 21) - 7; 41 | time = 1000 * 60 * 60 * 8 + 1000 * 60 * 30 * Math.floor(Math.random() * 24); 42 | duration = 1000 * 60 * 30 * (1 + Math.floor(Math.random() * 5)); 43 | 44 | events.push({ 45 | name: 'Event ' + events.length 46 | , start: moment().startOf('day').add('days', date - moment().day()).add('milliseconds', time) 47 | , end: moment().startOf('day').add('days', date - moment().day()).add('milliseconds', time + duration) 48 | }); 49 | } 50 | 51 | return events; 52 | }.property() 53 | }); -------------------------------------------------------------------------------- /examples/convertible/style.css: -------------------------------------------------------------------------------- 1 | /* The majority of these selectors + properties are required. 2 | However, you can customize them as you wish. */ 3 | 4 | .ember-calendar { 5 | border: 1px solid #ddd; 6 | font-family: 'myriad-pro', 'Myriad Pro', 'Calibri', 'Helvetica', 'Trebuchet MS', 'Verdana', sans-serif; 7 | height: 500px; 8 | width: 800px; 9 | } 10 | .ember-calendar-head { 11 | background: #ddd; 12 | color: #666; 13 | height: 25px; 14 | font-size: 14px; 15 | } 16 | .ember-calendar-container { 17 | height: 475px; 18 | } 19 | .ember-calendar-body { 20 | height: 1000px; 21 | } 22 | .ember-calendar-head-previous { 23 | width: 50px; /* A */ 24 | } 25 | .ember-calendar-head-next { 26 | width: 25px; /* B */ 27 | } 28 | .ember-calendar-head-dates { 29 | margin-left: 50px; /* A */ 30 | margin-right: 25px; /* B */ 31 | padding: 5px 0px; 32 | } 33 | .ember-calendar-head-times { 34 | border-right: 1px solid #f6f6f6; 35 | -moz-box-sizing: border-box; 36 | box-sizing: border-box; 37 | text-align: right; 38 | padding-right: 5px; 39 | width: 50px; /* A */ 40 | } 41 | .ember-calendar-head-time { 42 | color: #999; 43 | font-size: 13px; 44 | } 45 | .ember-calendar-days { 46 | margin-left: 50px; 47 | margin-right: 25px; 48 | } 49 | .ember-calendar-event { 50 | background: #f6f6f6; 51 | border: 1px solid #eee; 52 | -moz-box-sizing: border-box; 53 | box-sizing: border-box; 54 | padding: 5px; 55 | overflow: hidden; 56 | } 57 | .ember-calendar-event-name { 58 | color: #333; 59 | font-size: 14px; 60 | line-height: 16px; 61 | } 62 | .ember-calendar-event-time, .ember-calendar-event-location { 63 | color: #999; 64 | font-size: 12px; 65 | line-height: 14px; 66 | margin-bottom: 2px; 67 | } 68 | .ember-calendar-buttons { 69 | text-align: right; 70 | width: 802px; 71 | } 72 | .ember-calendar-buttons > button { 73 | background: none; 74 | border: none; 75 | box-shadow: none; 76 | color: #666; 77 | cursor: pointer; 78 | font-size: 12px; 79 | margin: 0px; 80 | margin-left: 5px; 81 | outline: none; 82 | padding: 5px 10px 4px; 83 | } 84 | .ember-calendar-buttons > button.ember-calendar-button-active { 85 | background: #ddd; 86 | } -------------------------------------------------------------------------------- /examples/multitype/style.css: -------------------------------------------------------------------------------- 1 | /* The majority of these selectors + properties are required. 2 | However, you can customize them as you wish. */ 3 | 4 | .ember-calendar { 5 | border: 1px solid #ddd; 6 | font-family: 'myriad-pro', 'Myriad Pro', 'Calibri', 'Helvetica', 'Trebuchet MS', 'Verdana', sans-serif; 7 | height: 500px; 8 | width: 800px; 9 | } 10 | .ember-calendar-head { 11 | background: #ddd; 12 | color: #666; 13 | height: 25px; 14 | font-size: 14px; 15 | } 16 | .ember-calendar-container { 17 | height: 475px; 18 | } 19 | .ember-calendar-body { 20 | height: 1000px; 21 | } 22 | .ember-calendar-head-previous { 23 | width: 50px; /* A */ 24 | } 25 | .ember-calendar-head-next { 26 | width: 25px; /* B */ 27 | } 28 | .ember-calendar-head-dates { 29 | margin-left: 50px; /* A */ 30 | margin-right: 25px; /* B */ 31 | padding: 5px 0px; 32 | } 33 | .ember-calendar-head-times { 34 | border-right: 1px solid #f6f6f6; 35 | -moz-box-sizing: border-box; 36 | box-sizing: border-box; 37 | text-align: right; 38 | padding-right: 5px; 39 | width: 50px; /* A */ 40 | } 41 | .ember-calendar-head-time { 42 | color: #999; 43 | font-size: 13px; 44 | } 45 | .ember-calendar-days { 46 | margin-left: 50px; 47 | margin-right: 25px; 48 | } 49 | .ember-calendar-event { 50 | background: #f6f6f6; 51 | border: 1px solid #eee; 52 | -moz-box-sizing: border-box; 53 | box-sizing: border-box; 54 | padding: 5px; 55 | overflow: hidden; 56 | } 57 | .ember-calendar-event-name { 58 | color: #333; 59 | font-size: 14px; 60 | line-height: 16px; 61 | } 62 | .ember-calendar-event-going, 63 | .ember-calendar-event-time, 64 | .ember-calendar-event-location { 65 | color: #999; 66 | font-size: 12px; 67 | line-height: 14px; 68 | margin-bottom: 2px; 69 | } 70 | .ember-calendar-event.spoton { 71 | background-color: #e03b59; 72 | border-color: #e03b59; 73 | } 74 | .ember-calendar-event.facebook { 75 | background-color: #8b9dc1; 76 | border-color: #7b8db1; 77 | } 78 | .ember-calendar-event.spoton .ember-calendar-event-name, 79 | .ember-calendar-event.facebook .ember-calendar-event-name { 80 | color: #fff; 81 | } 82 | .ember-calendar-event.spoton .ember-calendar-event-going, 83 | .ember-calendar-event.spoton .ember-calendar-event-time, 84 | .ember-calendar-event.spoton .ember-calendar-event-location, 85 | .ember-calendar-event.facebook .ember-calendar-event-time, 86 | .ember-calendar-event.facebook .ember-calendar-event-location { 87 | color: rgba(255, 255, 255, 0.7); 88 | } -------------------------------------------------------------------------------- /src/calendar.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 23 | 24 | 37 | 38 | 41 | 42 | 45 | 46 | 51 | 52 | 55 | 56 | 59 | -------------------------------------------------------------------------------- /examples/multitype/app.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Application 3 | /////////////////////////////////////////////////////////////////////////////// 4 | App = Ember.Application.create(); 5 | 6 | App.ApplicationController = Ember.Controller.extend({ 7 | needs: ['calendar'] 8 | }); 9 | 10 | App.ApplicationView = Ember.View.extend({ 11 | templateName: 'application' 12 | }); 13 | 14 | 15 | /////////////////////////////////////////////////////////////////////////////// 16 | // Views 17 | /////////////////////////////////////////////////////////////////////////////// 18 | App.EventView = Ember.Calendar.EventView.extend({ 19 | templateName: function () { 20 | return this.get('event.template') || 'ember-calendar-event'; 21 | }.property('event.template') 22 | 23 | , classNameBindings: ['facebook', 'google', 'spoton'] 24 | , spoton: function () { 25 | return this.get('event.type') === 0; 26 | }.property('event.type') 27 | , google: function () { 28 | return this.get('event.type') === 1; 29 | }.property('event.type') 30 | , facebook: function () { 31 | return this.get('event.type') === 2; 32 | }.property('event.type') 33 | }); 34 | 35 | 36 | /////////////////////////////////////////////////////////////////////////////// 37 | // Controller 38 | /////////////////////////////////////////////////////////////////////////////// 39 | App.CalendarController = Ember.Calendar.CalendarController.extend({ 40 | content: function () { 41 | var events = []; 42 | var date; 43 | var time; 44 | var duration; 45 | var event; 46 | 47 | for (var i = 0; i < 15; i++) { 48 | date = Math.floor(Math.random() * 7); 49 | time = 1000 * 60 * 60 * 8 + 1000 * 60 * 30 * Math.floor(Math.random() * 24); 50 | duration = 1000 * 60 * 30 * (1 + Math.floor(Math.random() * 5)); 51 | 52 | event = { 53 | name: 'Event ' + events.length 54 | , start: moment().startOf('day').add('days', date - moment().day()).add('milliseconds', time) 55 | , end: moment().startOf('day').add('days', date - moment().day()).add('milliseconds', time + duration) 56 | , type: Math.floor(Math.random() * 3) // 0 = spoton, 1 = google, 2 = facebook 57 | }; 58 | 59 | // add unique properties for spoton type 60 | if (event.type === 0) { 61 | event.numGoing = Math.floor(Math.random() * 100); 62 | event.template = 'ember-calendar-event-spoton'; 63 | } 64 | 65 | events.push(event); 66 | } 67 | 68 | return events; 69 | }.property() 70 | 71 | , eventViewClass: 'App.EventView' 72 | }); -------------------------------------------------------------------------------- /dist/ember-calendar.min.js: -------------------------------------------------------------------------------- 1 | /* ember-calendar 1.0.0 (https://github.com/joinspoton/ember-calendar) | (c) 2013 SpotOn (https://spoton.it) | http://www.opensource.org/licenses/MIT */ 2 | Ember.TEMPLATES["ember-calendar"]=Ember.Handlebars.compile("\n {{view Ember.Calendar.ButtonsView controllerBinding='controller'}}\n {{view Ember.Calendar.ContainerView controllerBinding='controller'}}\n"),Ember.TEMPLATES["ember-calendar-day"]=Ember.Handlebars.compile("\n\n\n
\n
\n
\n {{view Ember.Calendar.HeadingDateView dateBinding='controller.date'}}\n
\n
\n
\n
\n
\n {{view Ember.Calendar.HeadingTimesView timesBinding='controller.times'}}\n
\n {{view Ember.Calendar.DayView eventsBinding='controller.day'}}\n
\n
\n
\n"),Ember.TEMPLATES["ember-calendar-week"]=Ember.Handlebars.compile("\n\n\n
\n
\n {{view Ember.Calendar.WeekHeadingDatesView datesBinding='controller.weekDates'}}\n
\n
\n
\n
\n {{view Ember.Calendar.HeadingTimesView timesBinding='controller.times'}}\n {{view Ember.Calendar.WeekDaysView daysBinding='controller.weekDays'}}\n
\n
\n"),Ember.TEMPLATES["ember-calendar-head-date"]=Ember.Handlebars.compile("\n\n\n {{view.dateString}}\n"),Ember.TEMPLATES["ember-calendar-head-time"]=Ember.Handlebars.compile("\n\n\n {{view.timeString}}\n"),Ember.TEMPLATES["ember-calendar-event"]=Ember.Handlebars.compile("\n\n\n
{{view.nameString}}
\n
{{view.timeString}}
\n
{{view.locationString}}
\n"),Ember.TEMPLATES["ember-calendar-button-day"]=Ember.Handlebars.compile("\n\n\n Day\n"),Ember.TEMPLATES["ember-calendar-button-week"]=Ember.Handlebars.compile("\n\n\n Week\n"),Ember.Calendar=Ember.Namespace.create(),Ember.Calendar.CalendarController=Ember.ArrayController.extend({startOfWeek:0,startOfDay:8,headingDateFormat:"ddd MMM D",headingTimeFormat:"h a",headingTimeRangeStart:0,headingTimeRangeEnd:24,eventTimeFormat:"h:mm a",eventTimeSeparator:" - ",states:["week"],initialState:"week",presentState:"week",buttonViewClass:"Ember.Calendar.ButtonView",headingTimeViewClass:"Ember.Calendar.HeadingTimeView",eventViewClass:"Ember.Calendar.EventView",weekDayViewClass:"Ember.Calendar.WeekDayView",weekHeadingDateViewClass:"Ember.Calendar.WeekHeadingDateView",multipleStates:function(){return this.get("states").length>1}.property("states"),hasDayState:function(){return-1!==this.get("states").indexOf("day")}.property("states"),hasWeekState:function(){return-1!==this.get("states").indexOf("week")}.property("states"),stateIsDay:function(){return"day"===this.get("presentState")}.property("presentState"),stateIsWeek:function(){return"week"===this.get("presentState")}.property("presentState"),times:function(){var e,t=[];for(e=this.get("headingTimeRangeStart");et&&e.start<=n}).forEach(function(a){var r={};Object.keys(a).forEach(function(e){"start"!==e&&"end"!==e&&(r[e]=a[e])}),r.start=moment(t).startOf("day"),r.end=moment(n).endOf("day"),r.starta.end&&(r.end=moment(a.end)),e.push(r)}),e}.property("content","date"),week:null,weekDates:function(){if(!this.get("week"))return[];var e,t=this.get("week").clone().subtract("days",1),n=[];for(e=0;7>e;e++)n.push(t.add("days",1).clone());return n}.property("week"),weekDays:function(){if(!this.get("week"))return[];var e=[[],[],[],[],[],[],[]],t=this.get("weekDates"),n=this,a=t[0].clone(),r=t[6].clone().endOf("day");return this.get("content").forEach(function(t){var i,s,d,l,o=moment(t.start).clone(),m=moment(t.end).clone();if(!(a>=m||o>r))for(;m>o;){for(i={},s=Object.keys(t),d=0;dm&&(i.end=m.clone()),l=i.start.clone().startOf("day").diff(n.get("week"),"days"),l>=0&&6>=l&&e[l].push(i),o.add("days",1).startOf("day")}}),e}.property("content","weekDates"),actions:{loadPrevious:function(){"day"===this.get("presentState")?this.set("date",moment(this.get("date").clone().subtract("days",1))):"week"===this.get("presentState")&&this.set("week",moment(this.get("week").clone().subtract("days",7)))},loadNext:function(){"day"===this.get("presentState")?this.set("date",moment(this.get("date").clone().add("days",1))):"week"===this.get("presentState")&&this.set("week",moment(this.get("week").clone().add("days",7)))},changeState:function(e){e!==this.get("presentState")&&("day"===e?(this.set("date",moment(this.get("week"))),this.notifyPropertyChange("date"),this.set("presentState","day")):"week"===e&&(this.set("week",moment(this.get("date")).subtract("days",(moment(this.get("date")).day()+7-this.get("startOfWeek"))%7).startOf("day")),this.notifyPropertyChange("week"),this.set("presentState","week")))}},init:function(){this._super(),-1!==this.states.indexOf("day")&&this.set("date",moment(this.get("initialDate")).startOf("day")),-1!==this.states.indexOf("week")&&this.set("week",moment(this.get("initialDate")).subtract("days",(moment(this.get("initialDate")).day()+7-this.get("startOfWeek"))%7).startOf("day")),this.set("presentState",this.get("initialState"))}}),Ember.Calendar.CalendarView=Ember.View.extend({templateName:"ember-calendar"}),Ember.Calendar.ButtonsView=Ember.ContainerView.extend({classNames:["ember-calendar-buttons"],init:function(){this._super(),this.get("controller.multipleStates")&&(this.get("controller.hasDayState")&&this.pushObject(this.createChildView(Ember.get(this.get("parentView.controller.buttonViewClass")),{buttonState:"day",controller:this.get("controller")})),this.get("controller.hasWeekState")&&this.pushObject(this.createChildView(Ember.get(this.get("parentView.controller.buttonViewClass")),{buttonState:"week",controller:this.get("controller")})))}}),Ember.Calendar.ButtonView=Ember.View.extend({tagName:"button",classNames:["ember-calendar-button"],classNameBindings:["active:ember-calendar-button-active"],templateName:function(){return"ember-calendar-button-"+this.get("buttonState")}.property("buttonState"),templateNameDidChange:function(){this.rerender()}.observes("templateName"),active:function(){return this.get("controller.presentState")===this.get("buttonState")}.property("buttonState","controller.presentState"),click:function(){this.get("active")||this.get("controller").send("changeState",this.get("buttonState"))}}),Ember.Calendar.ContainerView=Ember.View.extend({classNames:["ember-calendar"],templateName:function(){return"ember-calendar-"+this.get("controller.presentState")}.property("controller.presentState"),templateNameDidChange:function(){this.rerender()}.observes("templateName"),didInsertElement:function(){if(-1!==["day","week"].indexOf(this.get("controller.presentState"))){var e=$("#"+this.get("elementId")+" > .ember-calendar-container"),t=$("#"+this.get("elementId")+" > .ember-calendar-container > .ember-calendar-body"),n=(this.get("controller.startOfDay")-this.get("controller.headingTimeRangeStart"))/(this.get("controller.headingTimeRangeEnd")-this.get("controller.headingTimeRangeStart"));e.scrollTop(n*t.height())}}}),Ember.Calendar.HeadingDateView=Ember.View.extend({templateName:"ember-calendar-head-date",classNames:["ember-calendar-head-date"],dateString:function(){return this.get("date").format(this.get("controller.headingDateFormat"))}.property("date")}),Ember.Calendar.HeadingTimesView=Ember.ContainerView.extend({classNames:["ember-calendar-head-times"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("times").map(function(t){return e.createChildView(Ember.get(e.get("controller.headingTimeViewClass")),{time:t})}))}.observes("times"),init:function(){this._super(),this.updateChildViews()}}),Ember.Calendar.HeadingTimeView=Ember.View.extend({templateName:"ember-calendar-head-time",classNames:["ember-calendar-head-time"],timeString:function(){return moment().startOf("day").add("milliseconds",this.get("time")).format(this.get("parentView.parentView.controller.headingTimeFormat"))}.property("time")}),Ember.Calendar.DayView=Ember.ContainerView.extend({classNames:["ember-calendar-day"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("events").map(function(t){return e.createChildView(Ember.get(e.get("parentView.controller.eventViewClass")),{event:t,parentView:e.get("parentView")})}))}.observes("events"),init:function(){this._super(),this.updateChildViews()}}),Ember.Calendar.EventView=Ember.View.extend({templateName:"ember-calendar-event",classNames:["ember-calendar-event"],attributeBindings:["style"],style:function(){if(!this.get("event"))return"";var e=moment(this.get("event.start")).valueOf(),t=moment(this.get("event.end")).valueOf(),n=moment(e).startOf("day").valueOf()+36e5*this.get("parentView.controller.headingTimeRangeStart"),a=moment(e).startOf("day").valueOf()+36e5*this.get("parentView.controller.headingTimeRangeEnd");return"top: "+100*(e-n)/(a-n)+"%; height: "+100*(t-e)/(a-n)+"%;"}.property("event","event.start","event.end"),nameString:function(){return this.get("event")?this.get("event.name"):""}.property("event","event.name"),timeString:function(){return this.get("event")?this.get("event.start").format(this.get("parentView.controller.eventTimeFormat"))+this.get("parentView.controller.eventTimeSeparator")+this.get("event.end").format(this.get("parentView.controller.eventTimeFormat")):""}.property("event","event.start","event.end"),locationString:function(){return this.get("event")&&this.get("event.location")?this.get("event.location.name")||this.get("event.location.address"):""}.property("event","event.location")}),Ember.Calendar.WeekHeadingDatesView=Ember.ContainerView.extend({classNames:["ember-calendar-head-dates"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("dates").map(function(t){return e.createChildView(Ember.get(e.get("parentView.controller.weekHeadingDateViewClass")),{date:t})}))}.observes("dates"),init:function(){this._super(),this.updateChildViews()}}),Ember.Calendar.WeekHeadingDateView=Ember.Calendar.HeadingDateView.extend({templateName:"ember-calendar-head-date",classNames:["ember-calendar-head-week-date"],dateString:function(){return this.get("date").format(this.get("parentView.parentView.controller.headingDateFormat"))}.property("date")}),Ember.Calendar.WeekDaysView=Ember.ContainerView.extend({classNames:["ember-calendar-days"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("days").map(function(t){return e.createChildView(Ember.get(e.get("parentView.controller.weekDayViewClass")),{events:t,parentView:e.get("parentView")})}))}.observes("days"),init:function(){this._super(),this.updateChildViews()}}),Ember.Calendar.WeekDayView=Ember.Calendar.DayView.extend({classNames:["ember-calendar-week-day"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("events").map(function(t){return e.createChildView(Ember.get(e.get("parentView.controller.eventViewClass")),{event:t,parentView:e.get("parentView")})}))}.observes("events")}); -------------------------------------------------------------------------------- /dist/ember-calendar.pre.min.js: -------------------------------------------------------------------------------- 1 | /* ember-calendar 1.0.0 (https://github.com/joinspoton/ember-calendar) | (c) 2013 SpotOn (https://spoton.it) | http://www.opensource.org/licenses/MIT */ 2 | Ember.TEMPLATES["ember-calendar"]=Ember.Handlebars.template(function(e,t,a,n,r){this.compilerInfo=[4,">= 1.0.0"],a=this.merge(a,Ember.Handlebars.helpers),r=r||{};var s,i,d="",l=this.escapeExpression;return r.buffer.push("\n "),s={controllerBinding:t},i={controllerBinding:"STRING"},r.buffer.push(l(a.view.call(t,"Ember.Calendar.ButtonsView",{hash:{controllerBinding:"controller"},contexts:[t],types:["ID"],hashContexts:s,hashTypes:i,data:r}))),r.buffer.push("\n "),s={controllerBinding:t},i={controllerBinding:"STRING"},r.buffer.push(l(a.view.call(t,"Ember.Calendar.ContainerView",{hash:{controllerBinding:"controller"},contexts:[t],types:["ID"],hashContexts:s,hashTypes:i,data:r}))),r.buffer.push("\n"),d}),Ember.TEMPLATES["ember-calendar-day"]=Ember.Handlebars.template(function(e,t,a,n,r){this.compilerInfo=[4,">= 1.0.0"],a=this.merge(a,Ember.Handlebars.helpers),r=r||{};var s,i,d="",l=this.escapeExpression;return r.buffer.push("\n\n\n
\n
\n
\n "),s={dateBinding:t},i={dateBinding:"STRING"},r.buffer.push(l(a.view.call(t,"Ember.Calendar.HeadingDateView",{hash:{dateBinding:"controller.date"},contexts:[t],types:["ID"],hashContexts:s,hashTypes:i,data:r}))),r.buffer.push("\n
\n
\n
\n
\n
\n "),s={timesBinding:t},i={timesBinding:"STRING"},r.buffer.push(l(a.view.call(t,"Ember.Calendar.HeadingTimesView",{hash:{timesBinding:"controller.times"},contexts:[t],types:["ID"],hashContexts:s,hashTypes:i,data:r}))),r.buffer.push("\n
\n "),s={eventsBinding:t},i={eventsBinding:"STRING"},r.buffer.push(l(a.view.call(t,"Ember.Calendar.DayView",{hash:{eventsBinding:"controller.day"},contexts:[t],types:["ID"],hashContexts:s,hashTypes:i,data:r}))),r.buffer.push("\n
\n
\n
\n"),d}),Ember.TEMPLATES["ember-calendar-week"]=Ember.Handlebars.template(function(e,t,a,n,r){this.compilerInfo=[4,">= 1.0.0"],a=this.merge(a,Ember.Handlebars.helpers),r=r||{};var s,i,d="",l=this.escapeExpression;return r.buffer.push("\n\n\n
\n
\n "),s={datesBinding:t},i={datesBinding:"STRING"},r.buffer.push(l(a.view.call(t,"Ember.Calendar.WeekHeadingDatesView",{hash:{datesBinding:"controller.weekDates"},contexts:[t],types:["ID"],hashContexts:s,hashTypes:i,data:r}))),r.buffer.push("\n
\n
\n
\n
\n "),s={timesBinding:t},i={timesBinding:"STRING"},r.buffer.push(l(a.view.call(t,"Ember.Calendar.HeadingTimesView",{hash:{timesBinding:"controller.times"},contexts:[t],types:["ID"],hashContexts:s,hashTypes:i,data:r}))),r.buffer.push("\n "),s={daysBinding:t},i={daysBinding:"STRING"},r.buffer.push(l(a.view.call(t,"Ember.Calendar.WeekDaysView",{hash:{daysBinding:"controller.weekDays"},contexts:[t],types:["ID"],hashContexts:s,hashTypes:i,data:r}))),r.buffer.push("\n
\n
\n"),d}),Ember.TEMPLATES["ember-calendar-head-date"]=Ember.Handlebars.template(function(e,t,a,n,r){this.compilerInfo=[4,">= 1.0.0"],a=this.merge(a,Ember.Handlebars.helpers),r=r||{};var s,i,d="",l=this.escapeExpression;return r.buffer.push("\n\n\n "),s={},i={},r.buffer.push(l(a._triageMustache.call(t,"view.dateString",{hash:{},contexts:[t],types:["ID"],hashContexts:i,hashTypes:s,data:r}))),r.buffer.push("\n"),d}),Ember.TEMPLATES["ember-calendar-head-time"]=Ember.Handlebars.template(function(e,t,a,n,r){this.compilerInfo=[4,">= 1.0.0"],a=this.merge(a,Ember.Handlebars.helpers),r=r||{};var s,i,d="",l=this.escapeExpression;return r.buffer.push("\n\n\n "),s={},i={},r.buffer.push(l(a._triageMustache.call(t,"view.timeString",{hash:{},contexts:[t],types:["ID"],hashContexts:i,hashTypes:s,data:r}))),r.buffer.push("\n"),d}),Ember.TEMPLATES["ember-calendar-event"]=Ember.Handlebars.template(function(e,t,a,n,r){this.compilerInfo=[4,">= 1.0.0"],a=this.merge(a,Ember.Handlebars.helpers),r=r||{};var s,i,d="",l=this.escapeExpression;return r.buffer.push("\n\n\n
"),s={},i={},r.buffer.push(l(a._triageMustache.call(t,"view.nameString",{hash:{},contexts:[t],types:["ID"],hashContexts:i,hashTypes:s,data:r}))),r.buffer.push("
\n
"),s={},i={},r.buffer.push(l(a._triageMustache.call(t,"view.timeString",{hash:{},contexts:[t],types:["ID"],hashContexts:i,hashTypes:s,data:r}))),r.buffer.push("
\n
"),s={},i={},r.buffer.push(l(a._triageMustache.call(t,"view.locationString",{hash:{},contexts:[t],types:["ID"],hashContexts:i,hashTypes:s,data:r}))),r.buffer.push("
\n"),d}),Ember.TEMPLATES["ember-calendar-button-day"]=Ember.Handlebars.template(function(e,t,a,n,r){this.compilerInfo=[4,">= 1.0.0"],a=this.merge(a,Ember.Handlebars.helpers),r=r||{},r.buffer.push("\n\n\n Day\n")}),Ember.TEMPLATES["ember-calendar-button-week"]=Ember.Handlebars.template(function(e,t,a,n,r){this.compilerInfo=[4,">= 1.0.0"],a=this.merge(a,Ember.Handlebars.helpers),r=r||{},r.buffer.push("\n\n\n Week\n")}),Ember.Calendar=Ember.Namespace.create(),Ember.Calendar.CalendarController=Ember.ArrayController.extend({startOfWeek:0,startOfDay:8,headingDateFormat:"ddd MMM D",headingTimeFormat:"h a",headingTimeRangeStart:0,headingTimeRangeEnd:24,eventTimeFormat:"h:mm a",eventTimeSeparator:" - ",states:["week"],initialState:"week",presentState:"week",buttonViewClass:"Ember.Calendar.ButtonView",headingTimeViewClass:"Ember.Calendar.HeadingTimeView",eventViewClass:"Ember.Calendar.EventView",weekDayViewClass:"Ember.Calendar.WeekDayView",weekHeadingDateViewClass:"Ember.Calendar.WeekHeadingDateView",multipleStates:function(){return this.get("states").length>1}.property("states"),hasDayState:function(){return-1!==this.get("states").indexOf("day")}.property("states"),hasWeekState:function(){return-1!==this.get("states").indexOf("week")}.property("states"),stateIsDay:function(){return"day"===this.get("presentState")}.property("presentState"),stateIsWeek:function(){return"week"===this.get("presentState")}.property("presentState"),times:function(){var e,t=[];for(e=this.get("headingTimeRangeStart");et&&e.start<=a}).forEach(function(n){var r={};Object.keys(n).forEach(function(e){"start"!==e&&"end"!==e&&(r[e]=n[e])}),r.start=moment(t).startOf("day"),r.end=moment(a).endOf("day"),r.startn.end&&(r.end=moment(n.end)),e.push(r)}),e}.property("content","date"),week:null,weekDates:function(){if(!this.get("week"))return[];var e,t=this.get("week").clone().subtract("days",1),a=[];for(e=0;7>e;e++)a.push(t.add("days",1).clone());return a}.property("week"),weekDays:function(){if(!this.get("week"))return[];var e=[[],[],[],[],[],[],[]],t=this.get("weekDates"),a=this,n=t[0].clone(),r=t[6].clone().endOf("day");return this.get("content").forEach(function(t){var s,i,d,l,o=moment(t.start).clone(),h=moment(t.end).clone();if(!(n>=h||o>r))for(;h>o;){for(s={},i=Object.keys(t),d=0;dh&&(s.end=h.clone()),l=s.start.clone().startOf("day").diff(a.get("week"),"days"),l>=0&&6>=l&&e[l].push(s),o.add("days",1).startOf("day")}}),e}.property("content","weekDates"),actions:{loadPrevious:function(){"day"===this.get("presentState")?this.set("date",moment(this.get("date").clone().subtract("days",1))):"week"===this.get("presentState")&&this.set("week",moment(this.get("week").clone().subtract("days",7)))},loadNext:function(){"day"===this.get("presentState")?this.set("date",moment(this.get("date").clone().add("days",1))):"week"===this.get("presentState")&&this.set("week",moment(this.get("week").clone().add("days",7)))},changeState:function(e){e!==this.get("presentState")&&("day"===e?(this.set("date",moment(this.get("week"))),this.notifyPropertyChange("date"),this.set("presentState","day")):"week"===e&&(this.set("week",moment(this.get("date")).subtract("days",(moment(this.get("date")).day()+7-this.get("startOfWeek"))%7).startOf("day")),this.notifyPropertyChange("week"),this.set("presentState","week")))}},init:function(){this._super(),-1!==this.states.indexOf("day")&&this.set("date",moment(this.get("initialDate")).startOf("day")),-1!==this.states.indexOf("week")&&this.set("week",moment(this.get("initialDate")).subtract("days",(moment(this.get("initialDate")).day()+7-this.get("startOfWeek"))%7).startOf("day")),this.set("presentState",this.get("initialState"))}}),Ember.Calendar.CalendarView=Ember.View.extend({templateName:"ember-calendar"}),Ember.Calendar.ButtonsView=Ember.ContainerView.extend({classNames:["ember-calendar-buttons"],init:function(){this._super(),this.get("controller.multipleStates")&&(this.get("controller.hasDayState")&&this.pushObject(this.createChildView(Ember.get(this.get("parentView.controller.buttonViewClass")),{buttonState:"day",controller:this.get("controller")})),this.get("controller.hasWeekState")&&this.pushObject(this.createChildView(Ember.get(this.get("parentView.controller.buttonViewClass")),{buttonState:"week",controller:this.get("controller")})))}}),Ember.Calendar.ButtonView=Ember.View.extend({tagName:"button",classNames:["ember-calendar-button"],classNameBindings:["active:ember-calendar-button-active"],templateName:function(){return"ember-calendar-button-"+this.get("buttonState")}.property("buttonState"),templateNameDidChange:function(){this.rerender()}.observes("templateName"),active:function(){return this.get("controller.presentState")===this.get("buttonState")}.property("buttonState","controller.presentState"),click:function(){this.get("active")||this.get("controller").send("changeState",this.get("buttonState"))}}),Ember.Calendar.ContainerView=Ember.View.extend({classNames:["ember-calendar"],templateName:function(){return"ember-calendar-"+this.get("controller.presentState")}.property("controller.presentState"),templateNameDidChange:function(){this.rerender()}.observes("templateName"),didInsertElement:function(){if(-1!==["day","week"].indexOf(this.get("controller.presentState"))){var e=$("#"+this.get("elementId")+" > .ember-calendar-container"),t=$("#"+this.get("elementId")+" > .ember-calendar-container > .ember-calendar-body"),a=(this.get("controller.startOfDay")-this.get("controller.headingTimeRangeStart"))/(this.get("controller.headingTimeRangeEnd")-this.get("controller.headingTimeRangeStart"));e.scrollTop(a*t.height())}}}),Ember.Calendar.HeadingDateView=Ember.View.extend({templateName:"ember-calendar-head-date",classNames:["ember-calendar-head-date"],dateString:function(){return this.get("date").format(this.get("controller.headingDateFormat"))}.property("date")}),Ember.Calendar.HeadingTimesView=Ember.ContainerView.extend({classNames:["ember-calendar-head-times"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("times").map(function(t){return e.createChildView(Ember.get(e.get("controller.headingTimeViewClass")),{time:t})}))}.observes("times"),init:function(){this._super(),this.updateChildViews()}}),Ember.Calendar.HeadingTimeView=Ember.View.extend({templateName:"ember-calendar-head-time",classNames:["ember-calendar-head-time"],timeString:function(){return moment().startOf("day").add("milliseconds",this.get("time")).format(this.get("parentView.parentView.controller.headingTimeFormat"))}.property("time")}),Ember.Calendar.DayView=Ember.ContainerView.extend({classNames:["ember-calendar-day"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("events").map(function(t){return e.createChildView(Ember.get(e.get("parentView.controller.eventViewClass")),{event:t,parentView:e.get("parentView")})}))}.observes("events"),init:function(){this._super(),this.updateChildViews()}}),Ember.Calendar.EventView=Ember.View.extend({templateName:"ember-calendar-event",classNames:["ember-calendar-event"],attributeBindings:["style"],style:function(){if(!this.get("event"))return"";var e=moment(this.get("event.start")).valueOf(),t=moment(this.get("event.end")).valueOf(),a=moment(e).startOf("day").valueOf()+36e5*this.get("parentView.controller.headingTimeRangeStart"),n=moment(e).startOf("day").valueOf()+36e5*this.get("parentView.controller.headingTimeRangeEnd");return"top: "+100*(e-a)/(n-a)+"%; height: "+100*(t-e)/(n-a)+"%;"}.property("event","event.start","event.end"),nameString:function(){return this.get("event")?this.get("event.name"):""}.property("event","event.name"),timeString:function(){return this.get("event")?this.get("event.start").format(this.get("parentView.controller.eventTimeFormat"))+this.get("parentView.controller.eventTimeSeparator")+this.get("event.end").format(this.get("parentView.controller.eventTimeFormat")):""}.property("event","event.start","event.end"),locationString:function(){return this.get("event")&&this.get("event.location")?this.get("event.location.name")||this.get("event.location.address"):""}.property("event","event.location")}),Ember.Calendar.WeekHeadingDatesView=Ember.ContainerView.extend({classNames:["ember-calendar-head-dates"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("dates").map(function(t){return e.createChildView(Ember.get(e.get("parentView.controller.weekHeadingDateViewClass")),{date:t})}))}.observes("dates"),init:function(){this._super(),this.updateChildViews()}}),Ember.Calendar.WeekHeadingDateView=Ember.Calendar.HeadingDateView.extend({templateName:"ember-calendar-head-date",classNames:["ember-calendar-head-week-date"],dateString:function(){return this.get("date").format(this.get("parentView.parentView.controller.headingDateFormat"))}.property("date")}),Ember.Calendar.WeekDaysView=Ember.ContainerView.extend({classNames:["ember-calendar-days"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("days").map(function(t){return e.createChildView(Ember.get(e.get("parentView.controller.weekDayViewClass")),{events:t,parentView:e.get("parentView")})}))}.observes("days"),init:function(){this._super(),this.updateChildViews()}}),Ember.Calendar.WeekDayView=Ember.Calendar.DayView.extend({classNames:["ember-calendar-week-day"],updateChildViews:function(){var e=this;e.removeAllChildren(),e.pushObjects(e.get("events").map(function(t){return e.createChildView(Ember.get(e.get("parentView.controller.eventViewClass")),{event:t,parentView:e.get("parentView")})}))}.observes("events")}); -------------------------------------------------------------------------------- /src/calendar.js: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // Namespace 3 | /////////////////////////////////////////////////////////////////////////////// 4 | Ember.Calendar = Ember.Namespace.create(); 5 | 6 | 7 | /////////////////////////////////////////////////////////////////////////////// 8 | // Controller 9 | /////////////////////////////////////////////////////////////////////////////// 10 | Ember.Calendar.CalendarController = Ember.ArrayController.extend({ 11 | startOfWeek: 0 12 | , startOfDay: 8 13 | , headingDateFormat: 'ddd MMM D' 14 | , headingTimeFormat: 'h a' 15 | , headingTimeRangeStart: 0 16 | , headingTimeRangeEnd: 24 17 | , eventTimeFormat: 'h:mm a' 18 | , eventTimeSeparator: ' - ' 19 | , states: ['week'] 20 | , initialState: 'week' 21 | , presentState: 'week' 22 | 23 | , buttonViewClass: 'Ember.Calendar.ButtonView' 24 | , headingTimeViewClass: 'Ember.Calendar.HeadingTimeView' 25 | , eventViewClass: 'Ember.Calendar.EventView' 26 | 27 | , weekDayViewClass: 'Ember.Calendar.WeekDayView' 28 | , weekHeadingDateViewClass: 'Ember.Calendar.WeekHeadingDateView' 29 | 30 | , multipleStates: function () { 31 | return this.get('states').length > 1; 32 | }.property('states') 33 | , hasDayState: function () { 34 | return this.get('states').indexOf('day') !== -1; 35 | }.property('states') 36 | , hasWeekState: function () { 37 | return this.get('states').indexOf('week') !== -1; 38 | }.property('states') 39 | , stateIsDay: function () { 40 | return this.get('presentState') === 'day'; 41 | }.property('presentState') 42 | , stateIsWeek: function () { 43 | return this.get('presentState') === 'week'; 44 | }.property('presentState') 45 | 46 | , times: function () { 47 | var times = []; 48 | var i; 49 | 50 | for (i = this.get('headingTimeRangeStart'); i < this.get('headingTimeRangeEnd'); i++) { 51 | times.push(1000 * 60 * 60 * i); 52 | } 53 | 54 | return times; 55 | }.property('headingTimeRangeStart', 'headingTimeRangeStart') 56 | 57 | , date: null 58 | , day: function () { 59 | if (!this.get('date')) { return []; } 60 | 61 | var day = []; 62 | var dayStart = this.get('date').clone(); 63 | var dayEnd = this.get('date').clone().endOf('day'); 64 | 65 | this.get('content').filter(function (event) { 66 | return event.end > dayStart && event.start <= dayEnd; 67 | }).forEach(function (event) { 68 | var object = {}; 69 | 70 | Object.keys(event).forEach(function (key) { 71 | if (key !== 'start' && key !== 'end') { 72 | object[key] = event[key]; 73 | } 74 | }); 75 | 76 | object.start = moment(dayStart).startOf('day'); 77 | object.end = moment(dayEnd).endOf('day'); 78 | if (object.start < event.start) { object.start = moment(event.start); } 79 | if (object.end > event.end) { object.end = moment(event.end); } 80 | 81 | day.push(object); 82 | }); 83 | 84 | return day; 85 | }.property('content', 'date') 86 | 87 | , week: null 88 | , weekDates: function () { 89 | if (!this.get('week')) { return []; } 90 | 91 | var curr = this.get('week').clone().subtract('days', 1); 92 | var dates = []; 93 | var i; 94 | 95 | for (i = 0; i < 7; i++) { 96 | dates.push(curr.add('days', 1).clone()); 97 | } 98 | 99 | return dates; 100 | }.property('week') 101 | , weekDays: function () { 102 | if (!this.get('week')) { return []; } 103 | 104 | var days = [[], [], [], [], [], [], []]; 105 | var dates = this.get('weekDates'); 106 | var self = this; 107 | var weekStart = dates[0].clone(); 108 | var weekEnd = dates[6].clone().endOf('day'); 109 | 110 | this.get('content').forEach(function (event) { 111 | var start = moment(event.start).clone(); 112 | var end = moment(event.end).clone(); 113 | var object; 114 | var keys; 115 | var i; 116 | var day; 117 | 118 | if (end <= weekStart || start > weekEnd) { return; } 119 | 120 | while (end > start) { 121 | object = {}; 122 | keys = Object.keys(event); 123 | 124 | for (i = 0; i < keys.length; i++) { 125 | if (keys[i] !== 'start' && keys[i] !== 'end') { 126 | object[keys[i]] = event[keys[i]]; 127 | } 128 | } 129 | 130 | object.start = start.clone(); 131 | object.end = start.clone().endOf('day'); 132 | if (object.end > end) { object.end = end.clone(); } 133 | 134 | day = object.start.clone().startOf('day').diff(self.get('week'), 'days'); 135 | if (day >= 0 && day <= 6) { days[day].push(object); } 136 | 137 | start.add('days', 1).startOf('day'); 138 | } 139 | }); 140 | 141 | return days; 142 | }.property('content', 'weekDates') 143 | 144 | , actions: { 145 | loadPrevious: function () { 146 | if (this.get('presentState') === 'day') { 147 | this.set('date', moment(this.get('date').clone().subtract('days', 1))); 148 | } else if (this.get('presentState') === 'week') { 149 | this.set('week', moment(this.get('week').clone().subtract('days', 7))); 150 | } 151 | } 152 | , loadNext: function () { 153 | if (this.get('presentState') === 'day') { 154 | this.set('date', moment(this.get('date').clone().add('days', 1))); 155 | } else if (this.get('presentState') === 'week') { 156 | this.set('week', moment(this.get('week').clone().add('days', 7))); 157 | } 158 | } 159 | , changeState: function (state) { 160 | if (state === this.get('presentState')) { 161 | return; 162 | } else if (state === 'day') { 163 | this.set('date', moment(this.get('week'))); 164 | this.notifyPropertyChange('date'); 165 | this.set('presentState', 'day'); 166 | } else if (state === 'week') { 167 | this.set('week', moment(this.get('date')).subtract('days', (moment(this.get('date')).day() + 7 - this.get('startOfWeek')) % 7).startOf('day')); 168 | this.notifyPropertyChange('week'); 169 | this.set('presentState', 'week'); 170 | } 171 | } 172 | } 173 | 174 | , init: function () { 175 | this._super(); 176 | 177 | if (this.states.indexOf('day') !== -1) { 178 | this.set('date', moment(this.get('initialDate')).startOf('day')); 179 | } 180 | 181 | if (this.states.indexOf('week') !== -1) { 182 | this.set('week', moment(this.get('initialDate')).subtract('days', (moment(this.get('initialDate')).day() + 7 - this.get('startOfWeek')) % 7).startOf('day')); 183 | } 184 | 185 | this.set('presentState', this.get('initialState')); 186 | } 187 | }); 188 | 189 | 190 | /////////////////////////////////////////////////////////////////////////////// 191 | // Generic Views 192 | /////////////////////////////////////////////////////////////////////////////// 193 | Ember.Calendar.CalendarView = Ember.View.extend({ 194 | templateName: 'ember-calendar' 195 | }); 196 | 197 | Ember.Calendar.ButtonsView = Ember.ContainerView.extend({ 198 | classNames: ['ember-calendar-buttons'] 199 | , init: function () { 200 | this._super(); 201 | 202 | if (!this.get('controller.multipleStates')) { return; } 203 | 204 | if (this.get('controller.hasDayState')) { 205 | this.pushObject(this.createChildView(Ember.get(this.get('parentView.controller.buttonViewClass')), { buttonState: 'day', controller: this.get('controller') })); 206 | } 207 | 208 | if (this.get('controller.hasWeekState')) { 209 | this.pushObject(this.createChildView(Ember.get(this.get('parentView.controller.buttonViewClass')), { buttonState: 'week', controller: this.get('controller') })); 210 | } 211 | } 212 | }); 213 | 214 | Ember.Calendar.ButtonView = Ember.View.extend({ 215 | tagName: 'button' 216 | , classNames: ['ember-calendar-button'] 217 | , classNameBindings: ['active:ember-calendar-button-active'] 218 | , templateName: function () { 219 | return 'ember-calendar-button-' + this.get('buttonState'); 220 | }.property('buttonState') 221 | , templateNameDidChange: function () { 222 | this.rerender(); 223 | }.observes('templateName') 224 | , active: function () { 225 | return this.get('controller.presentState') === this.get('buttonState'); 226 | }.property('buttonState', 'controller.presentState') 227 | , click: function () { 228 | if (this.get('active')) return; 229 | this.get('controller').send('changeState', this.get('buttonState')); 230 | } 231 | }); 232 | 233 | Ember.Calendar.ContainerView = Ember.View.extend({ 234 | classNames: ['ember-calendar'] 235 | , templateName: function () { 236 | return 'ember-calendar-' + this.get('controller.presentState'); 237 | }.property('controller.presentState') 238 | , templateNameDidChange: function () { 239 | this.rerender(); 240 | }.observes('templateName') 241 | , didInsertElement: function () { 242 | if (['day', 'week'].indexOf(this.get('controller.presentState')) !== -1) { 243 | var container = $('#' + this.get('elementId') + ' > .ember-calendar-container'); 244 | var body = $('#' + this.get('elementId') + ' > .ember-calendar-container > .ember-calendar-body'); 245 | var start = (this.get('controller.startOfDay') - this.get('controller.headingTimeRangeStart')) / (this.get('controller.headingTimeRangeEnd') - this.get('controller.headingTimeRangeStart')); 246 | 247 | container.scrollTop(start * body.height()); 248 | } 249 | } 250 | }); 251 | 252 | Ember.Calendar.HeadingDateView = Ember.View.extend({ 253 | templateName: 'ember-calendar-head-date' 254 | , classNames: ['ember-calendar-head-date'] 255 | , dateString: function () { 256 | return this.get('date').format(this.get('controller.headingDateFormat')); 257 | }.property('date') 258 | }); 259 | 260 | Ember.Calendar.HeadingTimesView = Ember.ContainerView.extend({ 261 | classNames: ['ember-calendar-head-times'] 262 | , updateChildViews: function () { 263 | var self = this; 264 | self.removeAllChildren(); 265 | self.pushObjects(self.get('times').map(function (time) { 266 | return self.createChildView(Ember.get(self.get('controller.headingTimeViewClass')), { time: time }); 267 | })); 268 | }.observes('times') 269 | , init: function () { 270 | this._super(); 271 | this.updateChildViews(); 272 | } 273 | }); 274 | 275 | Ember.Calendar.HeadingTimeView = Ember.View.extend({ 276 | templateName: 'ember-calendar-head-time' 277 | , classNames: ['ember-calendar-head-time'] 278 | , timeString: function () { 279 | return moment().startOf('day').add('milliseconds', this.get('time')).format(this.get('parentView.parentView.controller.headingTimeFormat')); 280 | }.property('time') 281 | }); 282 | 283 | Ember.Calendar.DayView = Ember.ContainerView.extend({ 284 | classNames: ['ember-calendar-day'] 285 | , updateChildViews: function () { 286 | var self = this; 287 | self.removeAllChildren(); 288 | self.pushObjects(self.get('events').map(function (event) { 289 | return self.createChildView(Ember.get(self.get('parentView.controller.eventViewClass')), { event: event, parentView: self.get('parentView') }); 290 | })); 291 | }.observes('events') 292 | , init: function () { 293 | this._super(); 294 | this.updateChildViews(); 295 | } 296 | }); 297 | 298 | Ember.Calendar.EventView = Ember.View.extend({ 299 | templateName: 'ember-calendar-event' 300 | , classNames: ['ember-calendar-event'] 301 | , attributeBindings: ['style'] 302 | , style: function () { 303 | if (!this.get('event')) { 304 | return ''; 305 | } 306 | 307 | var start = moment(this.get('event.start')).valueOf(); 308 | var end = moment(this.get('event.end')).valueOf(); 309 | var rangeStart = moment(start).startOf('day').valueOf() + 1000 * 60 * 60 * this.get('parentView.controller.headingTimeRangeStart'); 310 | var rangeEnd = moment(start).startOf('day').valueOf() + 1000 * 60 * 60 * this.get('parentView.controller.headingTimeRangeEnd'); 311 | 312 | return 'top: ' + 100 * (start - rangeStart) / (rangeEnd - rangeStart) + '%; height: ' + 100 * (end - start) / (rangeEnd - rangeStart) + '%;'; 313 | }.property('event', 'event.start', 'event.end') 314 | , nameString: function () { 315 | if (!this.get('event')) { 316 | return ''; 317 | } 318 | return this.get('event.name'); 319 | }.property('event', 'event.name') 320 | , timeString: function () { 321 | if (!this.get('event')) { 322 | return ''; 323 | } 324 | return this.get('event.start').format(this.get('parentView.controller.eventTimeFormat')) + this.get('parentView.controller.eventTimeSeparator') + this.get('event.end').format(this.get('parentView.controller.eventTimeFormat')); 325 | }.property('event', 'event.start', 'event.end') 326 | , locationString: function () { 327 | if (!this.get('event') || !this.get('event.location')) { 328 | return ''; 329 | } 330 | return this.get('event.location.name') || this.get('event.location.address'); 331 | }.property('event', 'event.location') 332 | }); 333 | 334 | 335 | /////////////////////////////////////////////////////////////////////////////// 336 | // Week Views 337 | /////////////////////////////////////////////////////////////////////////////// 338 | Ember.Calendar.WeekHeadingDatesView = Ember.ContainerView.extend({ 339 | classNames: ['ember-calendar-head-dates'] 340 | , updateChildViews: function () { 341 | var self = this; 342 | self.removeAllChildren(); 343 | self.pushObjects(self.get('dates').map(function (date) { 344 | return self.createChildView(Ember.get(self.get('parentView.controller.weekHeadingDateViewClass')), { date: date }); 345 | })); 346 | }.observes('dates') 347 | , init: function () { 348 | this._super(); 349 | this.updateChildViews(); 350 | } 351 | }); 352 | 353 | Ember.Calendar.WeekHeadingDateView = Ember.Calendar.HeadingDateView.extend({ 354 | templateName: 'ember-calendar-head-date' 355 | , classNames: ['ember-calendar-head-week-date'] 356 | , dateString: function () { 357 | return this.get('date').format(this.get('parentView.parentView.controller.headingDateFormat')); 358 | }.property('date') 359 | }); 360 | 361 | Ember.Calendar.WeekDaysView = Ember.ContainerView.extend({ 362 | classNames: ['ember-calendar-days'] 363 | , updateChildViews: function () { 364 | var self = this; 365 | self.removeAllChildren(); 366 | self.pushObjects(self.get('days').map(function (events) { 367 | return self.createChildView(Ember.get(self.get('parentView.controller.weekDayViewClass')), { events: events, parentView: self.get('parentView') }); 368 | })); 369 | }.observes('days') 370 | , init: function () { 371 | this._super(); 372 | this.updateChildViews(); 373 | } 374 | }); 375 | 376 | Ember.Calendar.WeekDayView = Ember.Calendar.DayView.extend({ 377 | classNames: ['ember-calendar-week-day'] 378 | , updateChildViews: function () { 379 | var self = this; 380 | self.removeAllChildren(); 381 | self.pushObjects(self.get('events').map(function (event) { 382 | return self.createChildView(Ember.get(self.get('parentView.controller.eventViewClass')), { event: event, parentView: self.get('parentView') }); 383 | })); 384 | }.observes('events') 385 | }); -------------------------------------------------------------------------------- /dist/ember-calendar.js: -------------------------------------------------------------------------------- 1 | /* ember-calendar 1.0.0 (https://github.com/joinspoton/ember-calendar) | (c) 2013 SpotOn (https://spoton.it) | http://www.opensource.org/licenses/MIT */ 2 | Ember.TEMPLATES["ember-calendar"] = Ember.Handlebars.compile("\n {{view Ember.Calendar.ButtonsView controllerBinding='controller'}}\n {{view Ember.Calendar.ContainerView controllerBinding='controller'}}\n"); 3 | Ember.TEMPLATES["ember-calendar-day"] = Ember.Handlebars.compile("\n\n\n
\n
\n
\n {{view Ember.Calendar.HeadingDateView dateBinding='controller.date'}}\n
\n
\n
\n
\n
\n {{view Ember.Calendar.HeadingTimesView timesBinding='controller.times'}}\n
\n {{view Ember.Calendar.DayView eventsBinding='controller.day'}}\n
\n
\n
\n"); 4 | Ember.TEMPLATES["ember-calendar-week"] = Ember.Handlebars.compile("\n\n\n
\n
\n {{view Ember.Calendar.WeekHeadingDatesView datesBinding='controller.weekDates'}}\n
\n
\n
\n
\n {{view Ember.Calendar.HeadingTimesView timesBinding='controller.times'}}\n {{view Ember.Calendar.WeekDaysView daysBinding='controller.weekDays'}}\n
\n
\n"); 5 | Ember.TEMPLATES["ember-calendar-head-date"] = Ember.Handlebars.compile("\n\n\n {{view.dateString}}\n"); 6 | Ember.TEMPLATES["ember-calendar-head-time"] = Ember.Handlebars.compile("\n\n\n {{view.timeString}}\n"); 7 | Ember.TEMPLATES["ember-calendar-event"] = Ember.Handlebars.compile("\n\n\n
{{view.nameString}}
\n
{{view.timeString}}
\n
{{view.locationString}}
\n"); 8 | Ember.TEMPLATES["ember-calendar-button-day"] = Ember.Handlebars.compile("\n\n\n Day\n"); 9 | Ember.TEMPLATES["ember-calendar-button-week"] = Ember.Handlebars.compile("\n\n\n Week\n"); 10 | /////////////////////////////////////////////////////////////////////////////// 11 | // Namespace 12 | /////////////////////////////////////////////////////////////////////////////// 13 | Ember.Calendar = Ember.Namespace.create(); 14 | 15 | 16 | /////////////////////////////////////////////////////////////////////////////// 17 | // Controller 18 | /////////////////////////////////////////////////////////////////////////////// 19 | Ember.Calendar.CalendarController = Ember.ArrayController.extend({ 20 | startOfWeek: 0 21 | , startOfDay: 8 22 | , headingDateFormat: 'ddd MMM D' 23 | , headingTimeFormat: 'h a' 24 | , headingTimeRangeStart: 0 25 | , headingTimeRangeEnd: 24 26 | , eventTimeFormat: 'h:mm a' 27 | , eventTimeSeparator: ' - ' 28 | , states: ['week'] 29 | , initialState: 'week' 30 | , presentState: 'week' 31 | 32 | , buttonViewClass: 'Ember.Calendar.ButtonView' 33 | , headingTimeViewClass: 'Ember.Calendar.HeadingTimeView' 34 | , eventViewClass: 'Ember.Calendar.EventView' 35 | 36 | , weekDayViewClass: 'Ember.Calendar.WeekDayView' 37 | , weekHeadingDateViewClass: 'Ember.Calendar.WeekHeadingDateView' 38 | 39 | , multipleStates: function () { 40 | return this.get('states').length > 1; 41 | }.property('states') 42 | , hasDayState: function () { 43 | return this.get('states').indexOf('day') !== -1; 44 | }.property('states') 45 | , hasWeekState: function () { 46 | return this.get('states').indexOf('week') !== -1; 47 | }.property('states') 48 | , stateIsDay: function () { 49 | return this.get('presentState') === 'day'; 50 | }.property('presentState') 51 | , stateIsWeek: function () { 52 | return this.get('presentState') === 'week'; 53 | }.property('presentState') 54 | 55 | , times: function () { 56 | var times = []; 57 | var i; 58 | 59 | for (i = this.get('headingTimeRangeStart'); i < this.get('headingTimeRangeEnd'); i++) { 60 | times.push(1000 * 60 * 60 * i); 61 | } 62 | 63 | return times; 64 | }.property('headingTimeRangeStart', 'headingTimeRangeStart') 65 | 66 | , date: null 67 | , day: function () { 68 | if (!this.get('date')) { return []; } 69 | 70 | var day = []; 71 | var dayStart = this.get('date').clone(); 72 | var dayEnd = this.get('date').clone().endOf('day'); 73 | 74 | this.get('content').filter(function (event) { 75 | return event.end > dayStart && event.start <= dayEnd; 76 | }).forEach(function (event) { 77 | var object = {}; 78 | 79 | Object.keys(event).forEach(function (key) { 80 | if (key !== 'start' && key !== 'end') { 81 | object[key] = event[key]; 82 | } 83 | }); 84 | 85 | object.start = moment(dayStart).startOf('day'); 86 | object.end = moment(dayEnd).endOf('day'); 87 | if (object.start < event.start) { object.start = moment(event.start); } 88 | if (object.end > event.end) { object.end = moment(event.end); } 89 | 90 | day.push(object); 91 | }); 92 | 93 | return day; 94 | }.property('content', 'date') 95 | 96 | , week: null 97 | , weekDates: function () { 98 | if (!this.get('week')) { return []; } 99 | 100 | var curr = this.get('week').clone().subtract('days', 1); 101 | var dates = []; 102 | var i; 103 | 104 | for (i = 0; i < 7; i++) { 105 | dates.push(curr.add('days', 1).clone()); 106 | } 107 | 108 | return dates; 109 | }.property('week') 110 | , weekDays: function () { 111 | if (!this.get('week')) { return []; } 112 | 113 | var days = [[], [], [], [], [], [], []]; 114 | var dates = this.get('weekDates'); 115 | var self = this; 116 | var weekStart = dates[0].clone(); 117 | var weekEnd = dates[6].clone().endOf('day'); 118 | 119 | this.get('content').forEach(function (event) { 120 | var start = moment(event.start).clone(); 121 | var end = moment(event.end).clone(); 122 | var object; 123 | var keys; 124 | var i; 125 | var day; 126 | 127 | if (end <= weekStart || start > weekEnd) { return; } 128 | 129 | while (end > start) { 130 | object = {}; 131 | keys = Object.keys(event); 132 | 133 | for (i = 0; i < keys.length; i++) { 134 | if (keys[i] !== 'start' && keys[i] !== 'end') { 135 | object[keys[i]] = event[keys[i]]; 136 | } 137 | } 138 | 139 | object.start = start.clone(); 140 | object.end = start.clone().endOf('day'); 141 | if (object.end > end) { object.end = end.clone(); } 142 | 143 | day = object.start.clone().startOf('day').diff(self.get('week'), 'days'); 144 | if (day >= 0 && day <= 6) { days[day].push(object); } 145 | 146 | start.add('days', 1).startOf('day'); 147 | } 148 | }); 149 | 150 | return days; 151 | }.property('content', 'weekDates') 152 | 153 | , actions: { 154 | loadPrevious: function () { 155 | if (this.get('presentState') === 'day') { 156 | this.set('date', moment(this.get('date').clone().subtract('days', 1))); 157 | } else if (this.get('presentState') === 'week') { 158 | this.set('week', moment(this.get('week').clone().subtract('days', 7))); 159 | } 160 | } 161 | , loadNext: function () { 162 | if (this.get('presentState') === 'day') { 163 | this.set('date', moment(this.get('date').clone().add('days', 1))); 164 | } else if (this.get('presentState') === 'week') { 165 | this.set('week', moment(this.get('week').clone().add('days', 7))); 166 | } 167 | } 168 | , changeState: function (state) { 169 | if (state === this.get('presentState')) { 170 | return; 171 | } else if (state === 'day') { 172 | this.set('date', moment(this.get('week'))); 173 | this.notifyPropertyChange('date'); 174 | this.set('presentState', 'day'); 175 | } else if (state === 'week') { 176 | this.set('week', moment(this.get('date')).subtract('days', (moment(this.get('date')).day() + 7 - this.get('startOfWeek')) % 7).startOf('day')); 177 | this.notifyPropertyChange('week'); 178 | this.set('presentState', 'week'); 179 | } 180 | } 181 | } 182 | 183 | , init: function () { 184 | this._super(); 185 | 186 | if (this.states.indexOf('day') !== -1) { 187 | this.set('date', moment(this.get('initialDate')).startOf('day')); 188 | } 189 | 190 | if (this.states.indexOf('week') !== -1) { 191 | this.set('week', moment(this.get('initialDate')).subtract('days', (moment(this.get('initialDate')).day() + 7 - this.get('startOfWeek')) % 7).startOf('day')); 192 | } 193 | 194 | this.set('presentState', this.get('initialState')); 195 | } 196 | }); 197 | 198 | 199 | /////////////////////////////////////////////////////////////////////////////// 200 | // Generic Views 201 | /////////////////////////////////////////////////////////////////////////////// 202 | Ember.Calendar.CalendarView = Ember.View.extend({ 203 | templateName: 'ember-calendar' 204 | }); 205 | 206 | Ember.Calendar.ButtonsView = Ember.ContainerView.extend({ 207 | classNames: ['ember-calendar-buttons'] 208 | , init: function () { 209 | this._super(); 210 | 211 | if (!this.get('controller.multipleStates')) { return; } 212 | 213 | if (this.get('controller.hasDayState')) { 214 | this.pushObject(this.createChildView(Ember.get(this.get('parentView.controller.buttonViewClass')), { buttonState: 'day', controller: this.get('controller') })); 215 | } 216 | 217 | if (this.get('controller.hasWeekState')) { 218 | this.pushObject(this.createChildView(Ember.get(this.get('parentView.controller.buttonViewClass')), { buttonState: 'week', controller: this.get('controller') })); 219 | } 220 | } 221 | }); 222 | 223 | Ember.Calendar.ButtonView = Ember.View.extend({ 224 | tagName: 'button' 225 | , classNames: ['ember-calendar-button'] 226 | , classNameBindings: ['active:ember-calendar-button-active'] 227 | , templateName: function () { 228 | return 'ember-calendar-button-' + this.get('buttonState'); 229 | }.property('buttonState') 230 | , templateNameDidChange: function () { 231 | this.rerender(); 232 | }.observes('templateName') 233 | , active: function () { 234 | return this.get('controller.presentState') === this.get('buttonState'); 235 | }.property('buttonState', 'controller.presentState') 236 | , click: function () { 237 | if (this.get('active')) return; 238 | this.get('controller').send('changeState', this.get('buttonState')); 239 | } 240 | }); 241 | 242 | Ember.Calendar.ContainerView = Ember.View.extend({ 243 | classNames: ['ember-calendar'] 244 | , templateName: function () { 245 | return 'ember-calendar-' + this.get('controller.presentState'); 246 | }.property('controller.presentState') 247 | , templateNameDidChange: function () { 248 | this.rerender(); 249 | }.observes('templateName') 250 | , didInsertElement: function () { 251 | if (['day', 'week'].indexOf(this.get('controller.presentState')) !== -1) { 252 | var container = $('#' + this.get('elementId') + ' > .ember-calendar-container'); 253 | var body = $('#' + this.get('elementId') + ' > .ember-calendar-container > .ember-calendar-body'); 254 | var start = (this.get('controller.startOfDay') - this.get('controller.headingTimeRangeStart')) / (this.get('controller.headingTimeRangeEnd') - this.get('controller.headingTimeRangeStart')); 255 | 256 | container.scrollTop(start * body.height()); 257 | } 258 | } 259 | }); 260 | 261 | Ember.Calendar.HeadingDateView = Ember.View.extend({ 262 | templateName: 'ember-calendar-head-date' 263 | , classNames: ['ember-calendar-head-date'] 264 | , dateString: function () { 265 | return this.get('date').format(this.get('controller.headingDateFormat')); 266 | }.property('date') 267 | }); 268 | 269 | Ember.Calendar.HeadingTimesView = Ember.ContainerView.extend({ 270 | classNames: ['ember-calendar-head-times'] 271 | , updateChildViews: function () { 272 | var self = this; 273 | self.removeAllChildren(); 274 | self.pushObjects(self.get('times').map(function (time) { 275 | return self.createChildView(Ember.get(self.get('controller.headingTimeViewClass')), { time: time }); 276 | })); 277 | }.observes('times') 278 | , init: function () { 279 | this._super(); 280 | this.updateChildViews(); 281 | } 282 | }); 283 | 284 | Ember.Calendar.HeadingTimeView = Ember.View.extend({ 285 | templateName: 'ember-calendar-head-time' 286 | , classNames: ['ember-calendar-head-time'] 287 | , timeString: function () { 288 | return moment().startOf('day').add('milliseconds', this.get('time')).format(this.get('parentView.parentView.controller.headingTimeFormat')); 289 | }.property('time') 290 | }); 291 | 292 | Ember.Calendar.DayView = Ember.ContainerView.extend({ 293 | classNames: ['ember-calendar-day'] 294 | , updateChildViews: function () { 295 | var self = this; 296 | self.removeAllChildren(); 297 | self.pushObjects(self.get('events').map(function (event) { 298 | return self.createChildView(Ember.get(self.get('parentView.controller.eventViewClass')), { event: event, parentView: self.get('parentView') }); 299 | })); 300 | }.observes('events') 301 | , init: function () { 302 | this._super(); 303 | this.updateChildViews(); 304 | } 305 | }); 306 | 307 | Ember.Calendar.EventView = Ember.View.extend({ 308 | templateName: 'ember-calendar-event' 309 | , classNames: ['ember-calendar-event'] 310 | , attributeBindings: ['style'] 311 | , style: function () { 312 | if (!this.get('event')) { 313 | return ''; 314 | } 315 | 316 | var start = moment(this.get('event.start')).valueOf(); 317 | var end = moment(this.get('event.end')).valueOf(); 318 | var rangeStart = moment(start).startOf('day').valueOf() + 1000 * 60 * 60 * this.get('parentView.controller.headingTimeRangeStart'); 319 | var rangeEnd = moment(start).startOf('day').valueOf() + 1000 * 60 * 60 * this.get('parentView.controller.headingTimeRangeEnd'); 320 | 321 | return 'top: ' + 100 * (start - rangeStart) / (rangeEnd - rangeStart) + '%; height: ' + 100 * (end - start) / (rangeEnd - rangeStart) + '%;'; 322 | }.property('event', 'event.start', 'event.end') 323 | , nameString: function () { 324 | if (!this.get('event')) { 325 | return ''; 326 | } 327 | return this.get('event.name'); 328 | }.property('event', 'event.name') 329 | , timeString: function () { 330 | if (!this.get('event')) { 331 | return ''; 332 | } 333 | return this.get('event.start').format(this.get('parentView.controller.eventTimeFormat')) + this.get('parentView.controller.eventTimeSeparator') + this.get('event.end').format(this.get('parentView.controller.eventTimeFormat')); 334 | }.property('event', 'event.start', 'event.end') 335 | , locationString: function () { 336 | if (!this.get('event') || !this.get('event.location')) { 337 | return ''; 338 | } 339 | return this.get('event.location.name') || this.get('event.location.address'); 340 | }.property('event', 'event.location') 341 | }); 342 | 343 | 344 | /////////////////////////////////////////////////////////////////////////////// 345 | // Week Views 346 | /////////////////////////////////////////////////////////////////////////////// 347 | Ember.Calendar.WeekHeadingDatesView = Ember.ContainerView.extend({ 348 | classNames: ['ember-calendar-head-dates'] 349 | , updateChildViews: function () { 350 | var self = this; 351 | self.removeAllChildren(); 352 | self.pushObjects(self.get('dates').map(function (date) { 353 | return self.createChildView(Ember.get(self.get('parentView.controller.weekHeadingDateViewClass')), { date: date }); 354 | })); 355 | }.observes('dates') 356 | , init: function () { 357 | this._super(); 358 | this.updateChildViews(); 359 | } 360 | }); 361 | 362 | Ember.Calendar.WeekHeadingDateView = Ember.Calendar.HeadingDateView.extend({ 363 | templateName: 'ember-calendar-head-date' 364 | , classNames: ['ember-calendar-head-week-date'] 365 | , dateString: function () { 366 | return this.get('date').format(this.get('parentView.parentView.controller.headingDateFormat')); 367 | }.property('date') 368 | }); 369 | 370 | Ember.Calendar.WeekDaysView = Ember.ContainerView.extend({ 371 | classNames: ['ember-calendar-days'] 372 | , updateChildViews: function () { 373 | var self = this; 374 | self.removeAllChildren(); 375 | self.pushObjects(self.get('days').map(function (events) { 376 | return self.createChildView(Ember.get(self.get('parentView.controller.weekDayViewClass')), { events: events, parentView: self.get('parentView') }); 377 | })); 378 | }.observes('days') 379 | , init: function () { 380 | this._super(); 381 | this.updateChildViews(); 382 | } 383 | }); 384 | 385 | Ember.Calendar.WeekDayView = Ember.Calendar.DayView.extend({ 386 | classNames: ['ember-calendar-week-day'] 387 | , updateChildViews: function () { 388 | var self = this; 389 | self.removeAllChildren(); 390 | self.pushObjects(self.get('events').map(function (event) { 391 | return self.createChildView(Ember.get(self.get('parentView.controller.eventViewClass')), { event: event, parentView: self.get('parentView') }); 392 | })); 393 | }.observes('events') 394 | }); -------------------------------------------------------------------------------- /dist/ember-calendar.pre.js: -------------------------------------------------------------------------------- 1 | /* ember-calendar 1.0.0 (https://github.com/joinspoton/ember-calendar) | (c) 2013 SpotOn (https://spoton.it) | http://www.opensource.org/licenses/MIT */ 2 | Ember.TEMPLATES["ember-calendar"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { 3 | this.compilerInfo = [4,'>= 1.0.0']; 4 | helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; 5 | var buffer = '', hashContexts, hashTypes, escapeExpression=this.escapeExpression; 6 | 7 | 8 | data.buffer.push("\n "); 9 | hashContexts = {'controllerBinding': depth0}; 10 | hashTypes = {'controllerBinding': "STRING"}; 11 | data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.Calendar.ButtonsView", {hash:{ 12 | 'controllerBinding': ("controller") 13 | },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 14 | data.buffer.push("\n "); 15 | hashContexts = {'controllerBinding': depth0}; 16 | hashTypes = {'controllerBinding': "STRING"}; 17 | data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.Calendar.ContainerView", {hash:{ 18 | 'controllerBinding': ("controller") 19 | },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 20 | data.buffer.push("\n"); 21 | return buffer; 22 | 23 | }); 24 | Ember.TEMPLATES["ember-calendar-day"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { 25 | this.compilerInfo = [4,'>= 1.0.0']; 26 | helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; 27 | var buffer = '', hashContexts, hashTypes, escapeExpression=this.escapeExpression; 28 | 29 | 30 | data.buffer.push("\n\n\n
\n
\n
\n "); 37 | hashContexts = {'dateBinding': depth0}; 38 | hashTypes = {'dateBinding': "STRING"}; 39 | data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.Calendar.HeadingDateView", {hash:{ 40 | 'dateBinding': ("controller.date") 41 | },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 42 | data.buffer.push("\n
\n
\n
\n
\n
\n "); 49 | hashContexts = {'timesBinding': depth0}; 50 | hashTypes = {'timesBinding': "STRING"}; 51 | data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.Calendar.HeadingTimesView", {hash:{ 52 | 'timesBinding': ("controller.times") 53 | },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 54 | data.buffer.push("\n
\n "); 55 | hashContexts = {'eventsBinding': depth0}; 56 | hashTypes = {'eventsBinding': "STRING"}; 57 | data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.Calendar.DayView", {hash:{ 58 | 'eventsBinding': ("controller.day") 59 | },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 60 | data.buffer.push("\n
\n
\n
\n"); 61 | return buffer; 62 | 63 | }); 64 | Ember.TEMPLATES["ember-calendar-week"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { 65 | this.compilerInfo = [4,'>= 1.0.0']; 66 | helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; 67 | var buffer = '', hashContexts, hashTypes, escapeExpression=this.escapeExpression; 68 | 69 | 70 | data.buffer.push("\n\n\n
\n
\n "); 77 | hashContexts = {'datesBinding': depth0}; 78 | hashTypes = {'datesBinding': "STRING"}; 79 | data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.Calendar.WeekHeadingDatesView", {hash:{ 80 | 'datesBinding': ("controller.weekDates") 81 | },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 82 | data.buffer.push("\n
\n
\n
\n
\n "); 89 | hashContexts = {'timesBinding': depth0}; 90 | hashTypes = {'timesBinding': "STRING"}; 91 | data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.Calendar.HeadingTimesView", {hash:{ 92 | 'timesBinding': ("controller.times") 93 | },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 94 | data.buffer.push("\n "); 95 | hashContexts = {'daysBinding': depth0}; 96 | hashTypes = {'daysBinding': "STRING"}; 97 | data.buffer.push(escapeExpression(helpers.view.call(depth0, "Ember.Calendar.WeekDaysView", {hash:{ 98 | 'daysBinding': ("controller.weekDays") 99 | },contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 100 | data.buffer.push("\n
\n
\n"); 101 | return buffer; 102 | 103 | }); 104 | Ember.TEMPLATES["ember-calendar-head-date"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { 105 | this.compilerInfo = [4,'>= 1.0.0']; 106 | helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; 107 | var buffer = '', hashTypes, hashContexts, escapeExpression=this.escapeExpression; 108 | 109 | 110 | data.buffer.push("\n\n\n "); 111 | hashTypes = {}; 112 | hashContexts = {}; 113 | data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.dateString", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 114 | data.buffer.push("\n"); 115 | return buffer; 116 | 117 | }); 118 | Ember.TEMPLATES["ember-calendar-head-time"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { 119 | this.compilerInfo = [4,'>= 1.0.0']; 120 | helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; 121 | var buffer = '', hashTypes, hashContexts, escapeExpression=this.escapeExpression; 122 | 123 | 124 | data.buffer.push("\n\n\n "); 125 | hashTypes = {}; 126 | hashContexts = {}; 127 | data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.timeString", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 128 | data.buffer.push("\n"); 129 | return buffer; 130 | 131 | }); 132 | Ember.TEMPLATES["ember-calendar-event"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { 133 | this.compilerInfo = [4,'>= 1.0.0']; 134 | helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; 135 | var buffer = '', hashTypes, hashContexts, escapeExpression=this.escapeExpression; 136 | 137 | 138 | data.buffer.push("\n\n\n
"); 139 | hashTypes = {}; 140 | hashContexts = {}; 141 | data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.nameString", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 142 | data.buffer.push("
\n
"); 143 | hashTypes = {}; 144 | hashContexts = {}; 145 | data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.timeString", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 146 | data.buffer.push("
\n
"); 147 | hashTypes = {}; 148 | hashContexts = {}; 149 | data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "view.locationString", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data}))); 150 | data.buffer.push("
\n"); 151 | return buffer; 152 | 153 | }); 154 | Ember.TEMPLATES["ember-calendar-button-day"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { 155 | this.compilerInfo = [4,'>= 1.0.0']; 156 | helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; 157 | 158 | 159 | 160 | data.buffer.push("\n\n\n Day\n"); 161 | 162 | }); 163 | Ember.TEMPLATES["ember-calendar-button-week"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) { 164 | this.compilerInfo = [4,'>= 1.0.0']; 165 | helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {}; 166 | 167 | 168 | 169 | data.buffer.push("\n\n\n Week\n"); 170 | 171 | }); 172 | /////////////////////////////////////////////////////////////////////////////// 173 | // Namespace 174 | /////////////////////////////////////////////////////////////////////////////// 175 | Ember.Calendar = Ember.Namespace.create(); 176 | 177 | 178 | /////////////////////////////////////////////////////////////////////////////// 179 | // Controller 180 | /////////////////////////////////////////////////////////////////////////////// 181 | Ember.Calendar.CalendarController = Ember.ArrayController.extend({ 182 | startOfWeek: 0 183 | , startOfDay: 8 184 | , headingDateFormat: 'ddd MMM D' 185 | , headingTimeFormat: 'h a' 186 | , headingTimeRangeStart: 0 187 | , headingTimeRangeEnd: 24 188 | , eventTimeFormat: 'h:mm a' 189 | , eventTimeSeparator: ' - ' 190 | , states: ['week'] 191 | , initialState: 'week' 192 | , presentState: 'week' 193 | 194 | , buttonViewClass: 'Ember.Calendar.ButtonView' 195 | , headingTimeViewClass: 'Ember.Calendar.HeadingTimeView' 196 | , eventViewClass: 'Ember.Calendar.EventView' 197 | 198 | , weekDayViewClass: 'Ember.Calendar.WeekDayView' 199 | , weekHeadingDateViewClass: 'Ember.Calendar.WeekHeadingDateView' 200 | 201 | , multipleStates: function () { 202 | return this.get('states').length > 1; 203 | }.property('states') 204 | , hasDayState: function () { 205 | return this.get('states').indexOf('day') !== -1; 206 | }.property('states') 207 | , hasWeekState: function () { 208 | return this.get('states').indexOf('week') !== -1; 209 | }.property('states') 210 | , stateIsDay: function () { 211 | return this.get('presentState') === 'day'; 212 | }.property('presentState') 213 | , stateIsWeek: function () { 214 | return this.get('presentState') === 'week'; 215 | }.property('presentState') 216 | 217 | , times: function () { 218 | var times = []; 219 | var i; 220 | 221 | for (i = this.get('headingTimeRangeStart'); i < this.get('headingTimeRangeEnd'); i++) { 222 | times.push(1000 * 60 * 60 * i); 223 | } 224 | 225 | return times; 226 | }.property('headingTimeRangeStart', 'headingTimeRangeStart') 227 | 228 | , date: null 229 | , day: function () { 230 | if (!this.get('date')) { return []; } 231 | 232 | var day = []; 233 | var dayStart = this.get('date').clone(); 234 | var dayEnd = this.get('date').clone().endOf('day'); 235 | 236 | this.get('content').filter(function (event) { 237 | return event.end > dayStart && event.start <= dayEnd; 238 | }).forEach(function (event) { 239 | var object = {}; 240 | 241 | Object.keys(event).forEach(function (key) { 242 | if (key !== 'start' && key !== 'end') { 243 | object[key] = event[key]; 244 | } 245 | }); 246 | 247 | object.start = moment(dayStart).startOf('day'); 248 | object.end = moment(dayEnd).endOf('day'); 249 | if (object.start < event.start) { object.start = moment(event.start); } 250 | if (object.end > event.end) { object.end = moment(event.end); } 251 | 252 | day.push(object); 253 | }); 254 | 255 | return day; 256 | }.property('content', 'date') 257 | 258 | , week: null 259 | , weekDates: function () { 260 | if (!this.get('week')) { return []; } 261 | 262 | var curr = this.get('week').clone().subtract('days', 1); 263 | var dates = []; 264 | var i; 265 | 266 | for (i = 0; i < 7; i++) { 267 | dates.push(curr.add('days', 1).clone()); 268 | } 269 | 270 | return dates; 271 | }.property('week') 272 | , weekDays: function () { 273 | if (!this.get('week')) { return []; } 274 | 275 | var days = [[], [], [], [], [], [], []]; 276 | var dates = this.get('weekDates'); 277 | var self = this; 278 | var weekStart = dates[0].clone(); 279 | var weekEnd = dates[6].clone().endOf('day'); 280 | 281 | this.get('content').forEach(function (event) { 282 | var start = moment(event.start).clone(); 283 | var end = moment(event.end).clone(); 284 | var object; 285 | var keys; 286 | var i; 287 | var day; 288 | 289 | if (end <= weekStart || start > weekEnd) { return; } 290 | 291 | while (end > start) { 292 | object = {}; 293 | keys = Object.keys(event); 294 | 295 | for (i = 0; i < keys.length; i++) { 296 | if (keys[i] !== 'start' && keys[i] !== 'end') { 297 | object[keys[i]] = event[keys[i]]; 298 | } 299 | } 300 | 301 | object.start = start.clone(); 302 | object.end = start.clone().endOf('day'); 303 | if (object.end > end) { object.end = end.clone(); } 304 | 305 | day = object.start.clone().startOf('day').diff(self.get('week'), 'days'); 306 | if (day >= 0 && day <= 6) { days[day].push(object); } 307 | 308 | start.add('days', 1).startOf('day'); 309 | } 310 | }); 311 | 312 | return days; 313 | }.property('content', 'weekDates') 314 | 315 | , actions: { 316 | loadPrevious: function () { 317 | if (this.get('presentState') === 'day') { 318 | this.set('date', moment(this.get('date').clone().subtract('days', 1))); 319 | } else if (this.get('presentState') === 'week') { 320 | this.set('week', moment(this.get('week').clone().subtract('days', 7))); 321 | } 322 | } 323 | , loadNext: function () { 324 | if (this.get('presentState') === 'day') { 325 | this.set('date', moment(this.get('date').clone().add('days', 1))); 326 | } else if (this.get('presentState') === 'week') { 327 | this.set('week', moment(this.get('week').clone().add('days', 7))); 328 | } 329 | } 330 | , changeState: function (state) { 331 | if (state === this.get('presentState')) { 332 | return; 333 | } else if (state === 'day') { 334 | this.set('date', moment(this.get('week'))); 335 | this.notifyPropertyChange('date'); 336 | this.set('presentState', 'day'); 337 | } else if (state === 'week') { 338 | this.set('week', moment(this.get('date')).subtract('days', (moment(this.get('date')).day() + 7 - this.get('startOfWeek')) % 7).startOf('day')); 339 | this.notifyPropertyChange('week'); 340 | this.set('presentState', 'week'); 341 | } 342 | } 343 | } 344 | 345 | , init: function () { 346 | this._super(); 347 | 348 | if (this.states.indexOf('day') !== -1) { 349 | this.set('date', moment(this.get('initialDate')).startOf('day')); 350 | } 351 | 352 | if (this.states.indexOf('week') !== -1) { 353 | this.set('week', moment(this.get('initialDate')).subtract('days', (moment(this.get('initialDate')).day() + 7 - this.get('startOfWeek')) % 7).startOf('day')); 354 | } 355 | 356 | this.set('presentState', this.get('initialState')); 357 | } 358 | }); 359 | 360 | 361 | /////////////////////////////////////////////////////////////////////////////// 362 | // Generic Views 363 | /////////////////////////////////////////////////////////////////////////////// 364 | Ember.Calendar.CalendarView = Ember.View.extend({ 365 | templateName: 'ember-calendar' 366 | }); 367 | 368 | Ember.Calendar.ButtonsView = Ember.ContainerView.extend({ 369 | classNames: ['ember-calendar-buttons'] 370 | , init: function () { 371 | this._super(); 372 | 373 | if (!this.get('controller.multipleStates')) { return; } 374 | 375 | if (this.get('controller.hasDayState')) { 376 | this.pushObject(this.createChildView(Ember.get(this.get('parentView.controller.buttonViewClass')), { buttonState: 'day', controller: this.get('controller') })); 377 | } 378 | 379 | if (this.get('controller.hasWeekState')) { 380 | this.pushObject(this.createChildView(Ember.get(this.get('parentView.controller.buttonViewClass')), { buttonState: 'week', controller: this.get('controller') })); 381 | } 382 | } 383 | }); 384 | 385 | Ember.Calendar.ButtonView = Ember.View.extend({ 386 | tagName: 'button' 387 | , classNames: ['ember-calendar-button'] 388 | , classNameBindings: ['active:ember-calendar-button-active'] 389 | , templateName: function () { 390 | return 'ember-calendar-button-' + this.get('buttonState'); 391 | }.property('buttonState') 392 | , templateNameDidChange: function () { 393 | this.rerender(); 394 | }.observes('templateName') 395 | , active: function () { 396 | return this.get('controller.presentState') === this.get('buttonState'); 397 | }.property('buttonState', 'controller.presentState') 398 | , click: function () { 399 | if (this.get('active')) return; 400 | this.get('controller').send('changeState', this.get('buttonState')); 401 | } 402 | }); 403 | 404 | Ember.Calendar.ContainerView = Ember.View.extend({ 405 | classNames: ['ember-calendar'] 406 | , templateName: function () { 407 | return 'ember-calendar-' + this.get('controller.presentState'); 408 | }.property('controller.presentState') 409 | , templateNameDidChange: function () { 410 | this.rerender(); 411 | }.observes('templateName') 412 | , didInsertElement: function () { 413 | if (['day', 'week'].indexOf(this.get('controller.presentState')) !== -1) { 414 | var container = $('#' + this.get('elementId') + ' > .ember-calendar-container'); 415 | var body = $('#' + this.get('elementId') + ' > .ember-calendar-container > .ember-calendar-body'); 416 | var start = (this.get('controller.startOfDay') - this.get('controller.headingTimeRangeStart')) / (this.get('controller.headingTimeRangeEnd') - this.get('controller.headingTimeRangeStart')); 417 | 418 | container.scrollTop(start * body.height()); 419 | } 420 | } 421 | }); 422 | 423 | Ember.Calendar.HeadingDateView = Ember.View.extend({ 424 | templateName: 'ember-calendar-head-date' 425 | , classNames: ['ember-calendar-head-date'] 426 | , dateString: function () { 427 | return this.get('date').format(this.get('controller.headingDateFormat')); 428 | }.property('date') 429 | }); 430 | 431 | Ember.Calendar.HeadingTimesView = Ember.ContainerView.extend({ 432 | classNames: ['ember-calendar-head-times'] 433 | , updateChildViews: function () { 434 | var self = this; 435 | self.removeAllChildren(); 436 | self.pushObjects(self.get('times').map(function (time) { 437 | return self.createChildView(Ember.get(self.get('controller.headingTimeViewClass')), { time: time }); 438 | })); 439 | }.observes('times') 440 | , init: function () { 441 | this._super(); 442 | this.updateChildViews(); 443 | } 444 | }); 445 | 446 | Ember.Calendar.HeadingTimeView = Ember.View.extend({ 447 | templateName: 'ember-calendar-head-time' 448 | , classNames: ['ember-calendar-head-time'] 449 | , timeString: function () { 450 | return moment().startOf('day').add('milliseconds', this.get('time')).format(this.get('parentView.parentView.controller.headingTimeFormat')); 451 | }.property('time') 452 | }); 453 | 454 | Ember.Calendar.DayView = Ember.ContainerView.extend({ 455 | classNames: ['ember-calendar-day'] 456 | , updateChildViews: function () { 457 | var self = this; 458 | self.removeAllChildren(); 459 | self.pushObjects(self.get('events').map(function (event) { 460 | return self.createChildView(Ember.get(self.get('parentView.controller.eventViewClass')), { event: event, parentView: self.get('parentView') }); 461 | })); 462 | }.observes('events') 463 | , init: function () { 464 | this._super(); 465 | this.updateChildViews(); 466 | } 467 | }); 468 | 469 | Ember.Calendar.EventView = Ember.View.extend({ 470 | templateName: 'ember-calendar-event' 471 | , classNames: ['ember-calendar-event'] 472 | , attributeBindings: ['style'] 473 | , style: function () { 474 | if (!this.get('event')) { 475 | return ''; 476 | } 477 | 478 | var start = moment(this.get('event.start')).valueOf(); 479 | var end = moment(this.get('event.end')).valueOf(); 480 | var rangeStart = moment(start).startOf('day').valueOf() + 1000 * 60 * 60 * this.get('parentView.controller.headingTimeRangeStart'); 481 | var rangeEnd = moment(start).startOf('day').valueOf() + 1000 * 60 * 60 * this.get('parentView.controller.headingTimeRangeEnd'); 482 | 483 | return 'top: ' + 100 * (start - rangeStart) / (rangeEnd - rangeStart) + '%; height: ' + 100 * (end - start) / (rangeEnd - rangeStart) + '%;'; 484 | }.property('event', 'event.start', 'event.end') 485 | , nameString: function () { 486 | if (!this.get('event')) { 487 | return ''; 488 | } 489 | return this.get('event.name'); 490 | }.property('event', 'event.name') 491 | , timeString: function () { 492 | if (!this.get('event')) { 493 | return ''; 494 | } 495 | return this.get('event.start').format(this.get('parentView.controller.eventTimeFormat')) + this.get('parentView.controller.eventTimeSeparator') + this.get('event.end').format(this.get('parentView.controller.eventTimeFormat')); 496 | }.property('event', 'event.start', 'event.end') 497 | , locationString: function () { 498 | if (!this.get('event') || !this.get('event.location')) { 499 | return ''; 500 | } 501 | return this.get('event.location.name') || this.get('event.location.address'); 502 | }.property('event', 'event.location') 503 | }); 504 | 505 | 506 | /////////////////////////////////////////////////////////////////////////////// 507 | // Week Views 508 | /////////////////////////////////////////////////////////////////////////////// 509 | Ember.Calendar.WeekHeadingDatesView = Ember.ContainerView.extend({ 510 | classNames: ['ember-calendar-head-dates'] 511 | , updateChildViews: function () { 512 | var self = this; 513 | self.removeAllChildren(); 514 | self.pushObjects(self.get('dates').map(function (date) { 515 | return self.createChildView(Ember.get(self.get('parentView.controller.weekHeadingDateViewClass')), { date: date }); 516 | })); 517 | }.observes('dates') 518 | , init: function () { 519 | this._super(); 520 | this.updateChildViews(); 521 | } 522 | }); 523 | 524 | Ember.Calendar.WeekHeadingDateView = Ember.Calendar.HeadingDateView.extend({ 525 | templateName: 'ember-calendar-head-date' 526 | , classNames: ['ember-calendar-head-week-date'] 527 | , dateString: function () { 528 | return this.get('date').format(this.get('parentView.parentView.controller.headingDateFormat')); 529 | }.property('date') 530 | }); 531 | 532 | Ember.Calendar.WeekDaysView = Ember.ContainerView.extend({ 533 | classNames: ['ember-calendar-days'] 534 | , updateChildViews: function () { 535 | var self = this; 536 | self.removeAllChildren(); 537 | self.pushObjects(self.get('days').map(function (events) { 538 | return self.createChildView(Ember.get(self.get('parentView.controller.weekDayViewClass')), { events: events, parentView: self.get('parentView') }); 539 | })); 540 | }.observes('days') 541 | , init: function () { 542 | this._super(); 543 | this.updateChildViews(); 544 | } 545 | }); 546 | 547 | Ember.Calendar.WeekDayView = Ember.Calendar.DayView.extend({ 548 | classNames: ['ember-calendar-week-day'] 549 | , updateChildViews: function () { 550 | var self = this; 551 | self.removeAllChildren(); 552 | self.pushObjects(self.get('events').map(function (event) { 553 | return self.createChildView(Ember.get(self.get('parentView.controller.eventViewClass')), { event: event, parentView: self.get('parentView') }); 554 | })); 555 | }.observes('events') 556 | }); --------------------------------------------------------------------------------