(function(angular) {
/**
* buildArray(size)
* Helper method for array creation. Needs to be checked regarding
* functionality in IE.
* @TODO optimize for speed?
*/
var buildArray = function(size) {
var array = [];
for(var index = 0; index < size; index ++) {
array.push(index);
}
return array;
};
/**
* This is an angularJS Calendar directive
*
* Possible parameters:
*
* <calendar
* events what events should be visible in this calendar
* view "hour", "day", "week", "month", "events"
* date date of the calendar, if week or month - view is
* used, it will be changed to the first day of the week
* format-day format of the labels for each day [default: 'dddd' or 'dddd, t\\he Do of MMMM']
* format-time format of the time label in the hour or events view [default: 'H a']
* format-week format of the weeknumber for the week view [default: 'wo']
* format ???
*
*/
var SECONDS_OF_A_DAY = 24*60*60
, SECONDS_MINIMAL = 60 * 30
, SLOT_WIDTH = 10
, PLUNKER = true
, DEFAULT_TIMEOUT = 0
, CALENDAR_HEIGHT = 1080
, MOMENT_HOURS = buildArray(24).map(function(index) {
return moment().hours(index).minutes(0).seconds(0)
})
/**
* sortEventByStartAndDuration(a, b)
* Simple Sorting function, which sort the values a and b by start time,
*/
var sortEventByStartAndDuration = function sortEventByStartAndDuration(a, b) {
return a.start.diff(b.start) || b.end.diff(b.start) - a.end.diff(a.start);
};
/**
* filterEventDuringDateRangeFactory(start, end) [function(event)]
* The filter addresses four different case:
* - event starts after item but item ends after event starts
* - item starts after event but also item start after event ends
* - item starts before event ends and also item ends after event start
* - the item-event does not take place during the event-event
*/
var filterEventDuringDateRangeFactory = function(start, end) {
return function(event) {
if(event.start.diff(start) < 0 && event.end.diff(start) > 0)
return true;
if(event.start.diff(start) > 0 && event.end.diff(start) < 0)
return true;
if(event.end.diff(start) > 0 && event.start.diff(end) < 0)
return true;
return false;
};
};
/**
* Verify if event is longer than 24hours.
*/
var isWholeDayEvent = function(event) {
return event.end.diff(event.start, 'seconds') >= SECONDS_OF_A_DAY;
};
/**
* colspanFactory()
*
*/
var colspanify = function colspanify(days, events) {
// return empty array if no days or events were given
if((!days.length) || (!events.length)) return;
var result = []
, slots = []
, length = days.length
, slot = 0
, firstDay = days[0].clone().sod();
events.map(function(event) {
var eventStart = event.start.clone().sod()
, eventEnd = event.end.clone().sod()
, slot = event.slot - 1;
current = slots[slot] = (slot in result ? slots[slot] : 0);
result[slot] = result[slot] || [];
var start = Math.min(length, Math.max(0, eventStart.diff(firstDay, 'days')))
, colspan = Math.max(1, Math.min(length - start, eventEnd.diff(eventStart, 'days') + 1));
if(slots[slot] < start) {
result[slot][result[slot].length] = {
colspan: start - slots[slot]
};
}
console.log('Calendar-Colspanify: ', colspan);
slots[slot] = start + colspan;
result[slot][result[slot].length] = {
colspan : colspan
, event : event
};
});
return result.map(function(list, index) {
if(slots[index] < length)
list[list.length] = {
colspan: length - slots[index]
};
return list;
});
};
/**
* slotFactory()
* Slotfactory creates different slots for events. That is necessary as they may overlapp
* regarding start and end time. Before using slotFactory it is necessary to order the
* list of events regarding time and length.
*/
var slotFactory = function(property) {
// [ { event: event, property: value } ]
return function slotify(item, index, list) {
// item = { event: event, property: value }
var slots = list.slice(0, index)
.filter(filterEventDuringDateRangeFactory(item.start, item.end))
.map(function(event) {
return event[property];
})
.filter(uniqueFilter)
.sort();
var slot = slots
.reduce(function(result, item, index) {
return !result && (item !== index + 1) ? index + 1 : result;
}, undefined);
/**
var result = {
event : item,
slot : slot || slots.length + 1
};
**/
item[property] = slot || slots.length + 1;
return item;
};
};
var timeout = function(scope, fn, timeout) {
// check if scope is set
if(typeof scope === "function") {
timeout = fn;
fn = scope;
scope = null;
}
var apply = (function(scope) {
if(scope) return function(fn, args) {
scope.$apply(function() {
fn.apply(self, args);
});
}
return function(fn, args) {
fn.apply(self, args);
}
})(scope)
return (function(timeout) {
var triggered = null;
return function() {
var args = Array.prototype.slice.call(arguments)
, self = this;
if(triggered)
clearTimeout(triggered);
triggered = setTimeout(function() {
triggered = null;
apply(fn, args);
}, timeout);
};
})(timeout || DEFAULT_TIMEOUT);
};
/**
* method get-start-of-week will return the first day of the week for any given day
*
* @param date day
* @return date
**/
var getStartOfWeek = function startOfWeek(day) {
// check if we have the monday-sunday problem
if(day.clone().day(1).diff(date)>0)
day.add('days', -7);
return day.day(1);
};
/**
* method hover-selektor will add and remove a klass for a selector when a given element is hovered
*
* @param DOM-elment element
* @param string selector
* @param string css-class
* @return element
**/
var hoverSelektor = function(element, selector, klass) {
return element.hover( function() { angular.element(selector).addClass(klass); }
, function() { angular.element(selector).removeClass(klass); }
);
};
/**
* unique-filter function to be applied with Array::filter
**/
var uniqueFilter = function(item, index, list) {
return list.indexOf(item, index + 1) < 0;
};
var calendarDirective = angular.module('directive.calendar', []);
calendarDirective.factory('eventEmitter', function() {
var events = {}
, seperator = '::'
var getEvent = function(event) {
return events[event] || (events[event] = [])
}
var Event = function(event) { this.event = event; }
var eventEmitter = {
create : function(event) {
return new Event(event || 'event');
}
, publish : function(event) {
var args = Array.prototype.slice.call(arguments, 1)
, Event = getEvent(event)
, self = this;
Event.map(function(handler) {
setTimeout(function() {
handler.apply(self, args);
}, DEFAULT_TIMEOUT);
});
}
, subscribe : function(event, handler) {
var Event = getEvent(event);
Event[Event.length] = handler;
}
, unsubscribe : function(event, handler) {
var Event = getEvent(event);
Event.filter(function(item) {
return item !== handler;
});
}
, clear : function(event) {
events[event] = [];
}
}
var curry = function(method) {
return function(event) {
var args = [[this.event, seperator, event].join('')].concat(Array.prototype.slice.call(arguments, 1));
eventEmitter[method].apply(this, args);
};
};
Object.keys(eventEmitter).map(function(method) {
Event.prototype[method] = curry(method);
});
return eventEmitter;
});
/**
* Calendar Event Service
*/
calendarDirective.factory('eventService', ['eventEmitter', function(eventEmitter) {
var rawDayEvents = []
, dayEvents = {}
, wholeDayEvents = []
, allEvents = []
, slotter = slotFactory('slot')
var addItemToList = function(item, list) {
if (list.indexOf(item) !== -1)
return true;
if (!eventExists(item, list)) {
var length = list.length;
//slotter(item, length, list)
list[length] = item;
}
};
var eventExists = function(item, list) {
var result = false;
angular.forEach(list, function(event) {
if (item.id === event.id)
result = true;
});
return result;
};
var EventEmitter = eventEmitter.create('eventService');
var eventService = {
storeEvents : function(events) {
if(!events) return;
dayEvents = {};
events.map(function (event) {
addItemToList(event, isWholeDayEvent(event) ?
wholeDayEvents : rawDayEvents);
addItemToList(event, allEvents);
});
[rawDayEvents, wholeDayEvents, allEvents].map(eventService.sortEvents);
[rawDayEvents, wholeDayEvents].map(function(list) { list.map(slotter); });
EventEmitter.publish('stored', events.length);
}
, appendEvents : function(events) {
EventEmitter.publish('store', events);
}
, sortEvents : function(events) {
return events.sort(sortEventByStartAndDuration);
}
, getDayEvents : function(day) {
var index = day.clone().sod()
if(!dayEvents[index]) {
var filter = filterEventDuringDateRangeFactory(day.clone().sod(), day.clone().eod())
dayEvents[index] = rawDayEvents.filter(filter);
}
return dayEvents[index];
}
, getWholeDayEvents : function(days) {
return wholeDayEvents;
}
, getAllEvent : function() {
return allEvents;
}
};
EventEmitter.subscribe('store', eventService.storeEvents)
return eventService;
}]);
calendarDirective.directive('wholeDayEvent', [function() {
return {
restrict: 'E'
, replace: true
, template: '<div name="bs-calendar-event-id-{{event.id}}" class="bs-calendar-whole-day-event-container evt-{{event.id}}">'
+ '<div class="bs-calendar-whole-day-event-container-inner color-{{event.colorId}}">'
+ '<div class="bs-calendar-whole-day-event-content">{{event.summary}}</div>'
+ '</div>'
+ '</div>'
, scope: {
event: '='
}
, link: function(scope, iElement, iAttr) {
if(scope.event !== undefined && scope.event.id)
hoverSelektor(iElement, '[name=bs-calendar-event-id-' + scope.event.id + ']', 'hovered');
}
};
}]);
calendarDirective.directive('hourViewNow', ['$compile', function($compile) {
return {
restrict: 'E'
, replace: true
, template: '<div>' +
'<div class="bs-tg-now"></div>' +
'</div>'
, link: function(scope, iElement, iAttribute) {
var day = scope.$parent.day
, display = 'none'
, now = moment()
, start_seconds = Math.max(now.diff(day.clone().sod(), 'seconds'), 0);
var height = CALENDAR_HEIGHT / SECONDS_OF_A_DAY;
if (!day.sod().diff(now.sod()))
display = 'block';
var nowDiv = angular.element(iElement.children("div.bs-tg-now"));
nowDiv.css({
display : display
, position: 'absolute'
, top : start_seconds * height
});
}
};
}]);
/**
*
*/
calendarDirective.directive('hourViewEvent', [function() {
return {
restrict: 'E'
, replace: true
, template: '<div name="bs-calendar-event-id-{{event.id}}" class="bs-calendar-event-container">'
+ '<div class="bs-calendar-event-content color-{{event.colorId}}">'
+ '<div class="bs-calendar-event-header">{{event.summary}}</div>'
+ '<div class="bs-calendar-event-body">{{event.description}}</div>'
+ '</div>'
+ '</div>'
, link: function postLink(scope, iElement, iAttrs) {
var day = scope.$parent.$parent.day
, start_seconds = Math.max(scope.event.start.diff(day.clone().sod(), 'seconds'), 0)
, end_seconds = Math.max((Math.min(scope.event.end.diff(day.clone().sod(), 'seconds'), SECONDS_OF_A_DAY) - start_seconds), SECONDS_MINIMAL)
;
var height = CALENDAR_HEIGHT / SECONDS_OF_A_DAY;
iElement.css({
left : ((scope.event.slot - 1) * SLOT_WIDTH) + '%'
, top : start_seconds * height
, height : end_seconds * height
});
if(scope.event.id)
hoverSelektor(iElement, '[name=bs-calendar-event-id-' + scope.event.id + ']', 'hovered');
}
};
}]);
/**
* dayViewCalendarDay is responsible for the creation of the basic table layout. It allows
* to create dynamic day-ranges to create calendars with ranges between 1 and 7 days.
*/
calendarDirective.directive('hourViewEventContainer', ['$compile', function($compile) {
return {
restrict: 'E'
, replace: true
, template: '<div class="bs-calendar-tg-day">'
+ '<hour-view-now></hour-view-now>'
+ '<hour-view-event ng-repeat="event in events"></hour-view-event>'
+ '</div>'
, scope: {
events: '='
}
, link: function(scope, iElement, iAttr) {
}
};
}]);
calendarDirective.directive('wholeDayView', ['eventService', function(eventService) {
return {
template: '<tr class="bs-calendar-header">'
+ '<td class="bs-calendar-weeknumber">'
+ '<div>{{days[0].format($parent.labelFormat)}}</div>'
+ '</td>'
+ '<td ng-repeat="day in days">'
+ '<div class="bs-calendar-daylabel">{{day.format($parent.$parent.dayLabelFormat)}}</div>'
+ '</td>'
+ '</tr>'
+ '<tr class="bs-calendar-whole-day-event-list" ng-repeat="wholeDayEvent in eventlist">'
+ '<td class="bs-calendar-whole-day-space"> </td>'
+ '<td ng-repeat="event in wholeDayEvent" colspan="{{event.colspan}}">'
+ '<whole-day-event event="event.event"></whole-day-event>'
+ '</td>'
+ '</tr>'
, scope: {
days: '=days'
}
, link: function(scope, iElement, iAttr) {
scope.$watch('days', function() {
scope.events = eventService.getWholeDayEvents(scope.days);
scope.eventlist = colspanify(scope.days, scope.events);
console.log('Calendar-Whole-Days: ', scope.days);
console.log('Calendar-Whole-Day-Events: ', scope.eventlist);
})
}
};
}]);
/**
*
*/
calendarDirective.directive('calendarDayView', ['eventService', function(eventService) {
return {
template: '<table class="table bs-calendar day-view">'
+ '<tbody whole-day-view days="days"></tbody>'
+ '<tbody>'
+ '<tr>'
+ '<td colspan="{{days.length + 1}}" class="bs-calendar-colwrapper">'
+ '<div class="bs-calendar-spanningwrapper">'
+ '<div class="bs-calendar-tg-hourmarkers">'
+ '<div class="bs-calendar-tg-markercell" ng-repeat="hour in hours">'
+ '<div class="bs-calendar-tg-dualmarker"></div>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '</td>'
+ '</tr>'
+ '<tr>'
+ '<td class="bs-calendar-tg-hours">'
+ '<div class="bs-calendar-tg-hour-inner" ng-repeat="hour in hours">'
+ '<div class="bs-calendar-tg-hour-clock">'
+ '{{hour.format(timeFormat)}}'
+ '</div>'
+ '</div>'
+ '</td>'
+ '<td ng-repeat="day in days" class="bs-calendar-tg-day-container" ng-class="{today: ((day.sod().diff(now.sod())!=0) + (numberOfDays > 1) == 1)}">'
+ '<hour-view-event-container events="day.events"></hour-view-event-container>'
+ '</td>'
+ '</tr>'
+ '</tbody>'
+ '</table>'
, link: function postLink(scope, iElement, iAttr) {
var attributes = scope.$parent.attributes
, numberOfDays = parseInt(attributes.days, 10) || 1
, offset = parseInt(attributes.offset, 10) || 0
, date = moment(scope.$parent.date)
, now = scope.$parent.now;
console.log('Calendar-Days: ', numberOfDays);
scope.days = [];
scope.timeFormat = attributes.timeFormat || 'H a';
scope.hours = MOMENT_HOURS;
var update = timeout(scope, function() {
var date = scope.$parent.date.clone().add('days', offset);
scope.days = buildArray(numberOfDays).map(function(index) {
var day = date.clone().add('days', index);
var events = eventService.getDayEvents(day);
day.events = events;
return day;
});
});
scope.$on('calendar-update', update);
}
};
}]);
calendarDirective.factory('daysOfWeek', function() {
return function(day) {
var base = day.clone(),
result = [];
for (var i = 0;i<7; i++)
result.push(base.add('days', i).clone());
return result;
};
});
/**
*
*/
calendarDirective.directive('calendarMonthView', ['daysOfWeek', function(daysOfWeek) {
return {
template: '<table class="table table-bordered table-striped bs-calendar">'
+ '<thead>'
+ '<th style="width: 15px">#</th>'
+ '<th ng-repeat="day in days">{{day.format("dddd")}}</th>'
+ '</thead>'
+ '<tbody>'
+ '<tr ng-repeat="week in weeks">'
+ '<td class="bs-calendar-month-row">{{week[1]}}</td>'
+ '<td ng-repeat="day in daysOfWeek[week[0]]" class="bs-calendar-month-row">{{day.format("DD")}}</td>'
+ '</tr>'
+ '</tbody>'
+ '</table>'
, link: function(scope, iElement, iAttr) {
var attributes = scope.$parent.attributes
, numberOfDays = 7
, numberOfWeeks = 5
, offset = parseInt(attributes.offset, 10) || 0
, date = moment(scope.$parent.date);
scope.daysOfWeek = {};
scope.days = buildArray(numberOfDays).map(function(index) {
return date.clone().add('days', index);
});
scope.weeks = buildArray(numberOfWeeks).map(function(index) {
var day = date.clone().add('weeks', index);
scope.daysOfWeek[index] = scope.daysOfWeek[index] || daysOfWeek(day);
return [index, day.format('w')];
});
}
};
}]);
/**
* This is the head of the calendar directive, it will determine
* what type of calendar is show.
* The possible views are: day, week, month, events
*
* day-view:
* This will show a vertical list of events a specific day
* week-view:
* This is an stretched day view with more than one day
* month-view:
* This is an stretched view accross n tables under each other
* events-view:
* This is an view, show all upcoming events in an ordered list
*/
calendarDirective.directive('calendar', ['eventService', function(eventService) {
return {
restrict: 'E'
, replace: true
, template: '<div class="calendar table-container" ng-switch on="view">'
+ '<div ng-switch-when="day" calendar-day-view></div>'
+ '<div ng-switch-when="month" calendar-month-view></div>'
+ '<div ng-switch-when="events" calendar-events-view></div>'
+ '</div>'
, scope: {
sourceEvents : '=eventsource'
, controlDate : '=date'
, calendarId : '@calendarId'
, confstartOfWeek : '@startOfWeek'
, confTimeFormat : '@timeFormat'
, confNumberOfDays : '@numberOfDays'
, confNumberOfWeeks : '@numberOfWeeks'
, confDayLabelFormat: '@dayLabelFormat'
}
, link: function(scope, iElement, iAttrs) {
scope.attributes = iAttrs;
scope.timeFormat = iAttrs.timeFormat || 'H a';
scope.dayLabelFormat= iAttrs.dayLabelFormat || (scope.startOfWeek ? 'dddd' : 'dddd, t\\he Do of MMMM');
scope.labelFormat = iAttrs.labelFormat || 'wo';
scope.offset = parseInt(iAttrs.offset, 10) || 0;
scope.now = moment();
scope.showHours = scope.numberOfWeeks === 1;
scope.date = moment(scope.controlDate);
scope.weeks = [];
scope.timeFormat = 'HH:mm DD.MM.YYYY';
scope.view = iAttrs.view;
}
};
}]);
})(window.angular);
<!DOCTYPE html>
<html ng-app="myApp">
<head lang="en">
<meta charset="utf-8">
<title>Bootstrap-AngularJS Calendar - Directive</title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.2/angular.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/moment.js/1.7.2/moment.min.js"></script>
<script src="components.js"></script>
<script src="app.js"></script>
<script src="controller.js"></script>
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap-combined.min.css"
rel="stylesheet">
<link href="calendar.css" rel="stylesheet">
<link href="calendar-colors.css" rel="stylesheet">
<link href="style.css" rel="stylesheet">
</head>
<body ng-controller="Ctrl">
<div class="container" style="margin-top: 20px;">
<!--
<ng-include src="'event-form.html'"></ng-include>
<ng-include src="'event-popup.html'"></ng-include>
-->
<tabs>
<pane pane-title="1 Days" pane-prev="date.add('days', -1)" pane-next="date.add('days', 1)">
<calendar
date="date"
view="day"
eventsource="events"
time-format="H a"
start-of-week="false"
days="1"
offset="0"
calendar-id="day"></calendar>
</pane>
<pane pane-title="4 Days" pane-prev="date.add('days', -1)" pane-next="date.add('days', 1)">
<calendar
date="date"
view="day"
eventsource="events"
time-format="H a"
start-of-week="false"
days="4"
offset="-2"
calendar-id="day"></calendar>
</pane>
<pane pane-title="7 Days" pane-prev="date.add('weeks', -1)" pane-next="date.add('weeks', 1)">
<calendar
date="date"
view="day"
eventsource="events"
time-format="H a"
start-of-week="false"
days="7"
offset="-3"
calendar-id="day"></calendar>
</pane>
<pane pane-title="Month" pane-prev="date.add('days', -1)" pane-next="date.add('days', 1)">
<calendar
date="date"
view="month"
days="7"
offset="-1"></calendar>
</calendar>
</pane>
</tabs>
</div>
</body>
</html>
/**
* @ToDo:
* Monatsansicht
*
*/
var calendarDirective = angular.module('directive.calendar', []);
var timeout = function(scope, fn) { return function() {
console.log('timeouting...', scope.$$phase)
scope.$apply(fn);
}; };
var getStartOfWeek = function startOfWeek(day) {
// check if we have the monday-sunday problem
if(day.clone().day(1).diff(date)>0)
day.add('days', -7);
return day.day(1);
};
var hoverSelektor = function(element, selektor, klass) {
element.hover( function() { angular.element(selektor).addClass(klass); }
, function() { angular.element(selektor).removeClass(klass); }
);
};
var SECONDS_OF_A_DAY = 24*60*60
, SLOT_WIDTH = 10
, PLUNKER = true;
var dateParser = function dateParser(event) {
};
var updateChain = function updateChain() {
var handler = [], length = 0;
var update = function Update(fn) {
handler[length] = fn;
length ++;
fn.apply(this, []);
};
update.trigger = function() {
var self = this, args = arguments;
angular.forEach(handler, function(fn) { fn.apply(self, args); });
};
return update;
};
/**
* sortEventByStartAndDuration(a, b)
* Simple Sorting function, which sort the values a and b by start time,
*/
var sortEventByStartAndDuration = function sortEventByStartAndDuration(a, b) {
return a.start.diff(b.start) || b.end.diff(b.start) - a.end.diff(a.start);
};
/**
* filterEventDuringEventFactory(event)
* The filter addresses four different case:
* - event starts after item but item ends after event starts
* - item starts after event but also item start after event ends
* - item starts before event ends and also item ends after event start
* - the item-event does not take place during the event-event
*/
var filterEventDuringDateRangeFactory = function(start, end) {
return function(event) {
if(event.start.diff(start) < 0 && event.end.diff(start) > 0)
return true;
if(event.start.diff(start) > 0 && event.end.diff(start) < 0)
return true;
if(event.end.diff(start) > 0 && event.start.diff(end) < 0)
return true;
return false;
};
};
/**
*
*/
var filterWholeDayEventDateRangeFactory = function(start, end) {
return function(event) {
if(start.diff(event.start) >= 0 && event.end.diff(start) >= 0)
return true;
if(end.diff(event.start) >= 0 && event.end.diff(end) >= 0)
return true;
return false;
};
};
/**
* slotFactory()
* Slotfactory creates different slots for events. That is necessary as they may overlapp
* regarding start and end time. Before using slotFactory it is necessary to order the
* list of events regarding time and length.
*/
var slotFactory = (function() {
var factory = function(assigner, modifier) {
return function slotify(item, index, list) {
var slots = list.slice(0, index)
.map(modifier)
.filter(filterEventDuringDateRangeFactory(item.start, item.end))
.map(function(event) {
return event.slot;
}).sort().filter(function(item, index, list) {
return list.indexOf(item) >= index;
})
, slot = slots
.reduce(function(result, item, index) {
if(result) return result;
if(item !== index + 1) return index + 1;
}, undefined);
return assigner(item, slot || slots.length + 1);
};
};
var slotter = function(assigner, modifier) {
return factory(typeof assigner === 'function' ? assigner : function(item, slot) {
item.slot = slot;
return item;
}, typeof modifier === 'function' ? modifier : function(item) {
return angular.copy(item);
});
};
return slotter;
})();
/**
* buildArray(size)
* Helper method for array creation. Needs to be checked regarding
* functionality in IE.
*/
var buildArray = function(size) {
var array = [];
for(var index = 0; index < size; index ++) {
array.push(index);
}
return array;
};
calendarDirective.directive('wholeDayEvent', [function() {
return {
restrict: 'E',
replace: true,
template: '<div name="bs-calendar-event-id-{{event.id}}" class="bs-calendar-whole-day-event-container evt-{{event.id}}">' +
'<div class="bs-calendar-whole-day-event-container-inner color-{{event.colorId}}">' +
'<div class="bs-calendar-whole-day-event-content">{{event.summary}}</div>' +
'</div>' +
'</div>',
scope: {
event: '='
}
, link: function(scope, iElement, iAttr) {
if(scope.event.id) hoverSelektor(iElement, '[name=bs-calendar-event-id-' + scope.event.id + ']', 'hovered');
}
};
}]);
calendarDirective.directive('hourViewNow', ['$compile', function($compile) {
return {
restrict: 'E'
, replace: true
, template: '<div>' +
'<div class="bs-tg-now"></div>' +
'</div>'
, link: function(scope, iElement, iAttribute) {
var day = scope.$parent.day
, display = 'none'
, now = moment()
, start_seconds = Math.max(now.diff(day.clone().sod(), 'seconds'), 0)
scope.$on('calendar-update', timeout(scope, function() {
var height = scope.height / SECONDS_OF_A_DAY
if (day.sod().diff(now.sod()) == 0)
display = 'block';
var nowDiv = angular.element(iElement.children("div.bs-tg-now"));
nowDiv.css(x = {
display : display
, position: 'absolute'
, top : start_seconds * height
});
}));
}
};
}]);
/**
*
*/
calendarDirective.directive('hourViewEvent', [function() {
var eventcounter = 0;
return {
restrict: 'E'
, replace: true
, template: '<div name="bs-calendar-event-id-{{event.id}}" class="bs-calendar-event-container">' +
'<div class="bs-calendar-event-content color-{{event.colorId}}">' +
'<div class="bs-calendar-event-header">{{event.summary}}</div>' +
'<div class="bs-calendar-event-body">{{event.description}}</div>' +
'</div>' +
'</div>'
, link: function(scope, iElement, iAttr) {
var day = scope.$parent.$parent.day
, start_seconds = Math.max(scope.event.start.diff(day.clone().sod(), 'seconds'), 0)
, end_seconds = (Math.min(scope.event.end.diff(day.clone().sod(), 'seconds'), SECONDS_OF_A_DAY) - start_seconds)
;
scope.$on('calendar-update', timeout(scope, function() {
var height = scope.$parent.height / SECONDS_OF_A_DAY
iElement.css({
left : ((scope.event.slot - 1) * SLOT_WIDTH) + '%'
, top : start_seconds * height
, height : end_seconds * height
});
if(scope.event.id)
hoverSelektor(iElement, '[name=bs-calendar-event-id-' + scope.event.id + ']', 'hovered');
}));
}
};
}]);
/**
* dayViewCalendarDay is responsible for the creation of the basic table layout. It allows
* to create dynamic day-ranges to create calendars with ranges between 1 and 7 days.
*/
calendarDirective.directive('hourViewEventContainer', ['$compile', function($compile) {
return {
restrict: 'E'
, replace: true
, template: '<div class="bs-calendar-tg-day">' +
'<hour-view-now></hour-view-now>' +
'<hour-view-event ng-repeat="event in events"></hour-view-event>' +
'</div>'
, scope: {
events: '='
}
, link: function(scope, iElement, iAttr) {
scope.height = 0; // just some default value, should be changed before use
scope.$on('calendar-update', function() {
scope.height = angular.element(iElement).innerHeight();
});
}
};
}]);
calendarDirective.directive('wholeDayView', [function() {
return {
require: '^calendar'
, restrict: 'A'
, template:
'<tr class="bs-calendar-header">' +
'<td class="bs-calendar-weeknumber">' +
'<div>{{days[0].format($parent.labelFormat)}}</div>' +
'</td>' +
'<td ng-repeat="day in days" ng-class="{today: ((day.sod().diff($parent.now.sod())!=0) + ($parent.numberOfDays > 1) == 1)}">' +
'<div class="bs-calendar-daylabel">{{day.format($parent.$parent.dayLabelFormat)}}</div>' +
'</td>' +
'</tr>' +
'<tr class="bs-calendar-whole-day-event-list" ng-repeat="wholeDayEvent in getEventsOfWeek()">' +
'<td> </td>' +
'<td ng-repeat="event in wholeDayEvent.list" colspan="{{event.colspan}}">' +
'<whole-day-event event="event"></whole-day-event>' +
'</td>' +
'</tr>'
, scope: {
days: '=wholeDayView'
}
, link: function(scope, iElement, iAttr, calendarController) {
scope.eventCache = [];
scope.getEventsOfWeek = function() {
return scope.eventCache = scope.eventCache.length
? scope.eventCache
: calendarController.getWholeDayEvents(scope.days);
};
// emit the update
scope.$on('calendar-update', function() {
scope.eventCache = [];
});
}
};
}]);
/**
*
*/
calendarDirective.directive('hourView', [function() {
return {
require: '^calendar'
, restrict: 'A'
, template:
'<tr>' +
'<td colspan="{{days.length + 1}}" class="bs-calendar-colwrapper">' +
'<div class="bs-calendar-spanningwrapper">' +
'<div class="bs-calendar-tg-hourmarkers">' +
'<div class="bs-calendar-tg-markercell" ng-repeat="hour in hours">' +
'<div class="bs-calendar-tg-dualmarker"></div>' +
'</div>' +
'</div>' +
'</div>' +
'</td>' +
'</tr>' +
'<tr>' +
'<td class="bs-calendar-tg-hours">' +
'<div class="bs-calendar-tg-hour-inner" ng-repeat="hour in hours">' +
'<div class="bs-calendar-tg-hour-clock">' +
'{{hour.format(timeFormat)}}' +
'</div>' +
'</div>' +
'</td>' +
'<td ng-repeat="day in days" class="bs-calendar-tg-day-container" ng-class="{today: ((day.sod().diff(now.sod())!=0) + (numberOfDays > 1) == 1)}">' +
'{{day.format("DD.MM.YYYY")}}<hour-view-event-container events="getEventsOfDay(day)"></hour-view-event-container>' +
'</td>' +
'</tr>'
, link: function(scope, iElement, iAttr, calendarController) {
scope.eventCache = {}
scope.getEventsOfDay = function(day) {
var sod = day.clone().sod();
if(sod in scope.eventCache)
return scope.eventCache[sod];
return scope.eventCache[sod] = calendarController.getEvents(sod, day.clone().eod());
}
// emit the update
scope.$on('calendar-update', function() {
scope.eventCache = {};
})
}
};
}]);
/**
* dayViewCalendar is responsible for the creation of the basic table layout. It allows
* to create dynamic day-ranges to create calendars with ranges between 1 and 7 days.
*/
calendarDirective.directive('calendar', [function() {
return {
restrict: 'E'
, replace: true
, template:
'<div class="table-container">' +
'<table ng-switch="weeks.length" id="{{calendarId}}" class="table table-bordered table-striped bs-calendar" ng-switch="numberOfWeeks">' +
'<tbody whole-day-view="week" ng-repeat="week in weeks"></tbody>' +
'<tbody hour-view="daysEventList" ng-switch-when="1" days="weeks[0]"></tbody>' +
'</table>' +
'</div>'
, scope: {
events : '=eventsource'
, controlDate : '=date'
// config elements that will have default values
, calendarId : '@calendarId'
, confstartOfWeek : '@startOfWeek'
, confTimeFormat : '@timeFormat'
, confNumberOfDays : '@numberOfDays'
, confNumberOfWeeks : '@numberOfWeeks'
, confDayLabelFormat: '@dayLabelFormat'
}
, controller: ['$scope', function(scope, $element) {
this.getEvents = function getEvents(start, end) {
var wholeDayEventFilter = filterWholeDayEventDateRangeFactory(start, end);
var events = angular.copy(scope.events)
.sort(sortEventByStartAndDuration)
.filter(filterEventDuringDateRangeFactory(start, end))
.filter(function(event) {
return !wholeDayEventFilter(event);
})
.map(slotFactory());
return events;
};
this.getWholeDayEvents = function getWholeDayEvents(days) {
var numberOfDays = days.length
, date = days[0]
// prepare everything to create the td-tr listing from the slots
, wholeDayEventList = []
, slots = []
// add-item-to-slot adds a colspan to an item
, addItemToSlot = function addItemToSlot(slot, start, event) {
event = event || {};
event.colspan = start - slots[slot] + 1;
slots[slot] = start + 1;
wholeDayEventList[slot].list[wholeDayEventList[slot].list.length] = event;
}
, fillWithEmpty = function fillWithEmpty(slot, number) {
for(var current = slots[slot]; current < number; current ++) {
// add empty item
addItemToSlot(slot, current);
}
}
// add-events-to-slot will add an event to a slot, and checking for
// holes in the colspan list. it will add an empty item there
, addEventsToSlot = function addEventsToSlot(event) {
// slot to work with and start/end in numbers of the event
var slot = event.slot - 1
, start = Math.min(numberOfDays - 1
, Math.max(0
, event.start.clone().sod().diff(date.clone().sod(), 'days')
))
, end = Math.min(numberOfDays - 1
, Math.max(0
, event.end.clone().sod().diff(date.clone().sod(), 'days')
));
// if slot was not used, fill it with empty data
if(!(slot in slots)) {
wholeDayEventList[slot] = { slot: slot, list: [] };
slots[slot] = 0;
}
// fill until we reach the start of this event
// this works because the events are sorted
fillWithEmpty(slot, start);
// add event itself
addItemToSlot(slot, end, event);
};
angular.copy(scope.events)
// first sort them by start and duration
.sort(sortEventByStartAndDuration)
// then filter any event that is not longer then any
// day we are looking at
.filter(function(event) {
return days.reduce(function(current, day) {
return current
|| filterWholeDayEventDateRangeFactory(day.clone().sod(), day.clone().eod())(event);
}, false);
// then slot them, by creating a slotter which will modify
// the dates so we are looking only at sod and eod dates
}).map(slotFactory(null, function(item) {
var result = angular.copy(item);
result.start = result.start.clone().sod();
result.end = result.end.clone().eod();
return result;
// add all whole day events to their slots
})).map(addEventsToSlot);
// check each slot and fill them up
slots.map(function(_, slot) { fillWithEmpty(slot, numberOfDays); });
// finally return the list
return wholeDayEventList;
}
}]
, link: function(scope, iElement, iAttrs) {
scope.numberOfWeeks = Math.max(1, parseInt(iAttrs.numberOfWeeks, 10) || 1);
scope.numberOfDays = Math.max(1, parseInt(iAttrs.numberOfDays, 10) || 7);
scope.startOfWeek = scope.$parent.$eval(iAttrs.startOfWeek); // @ check
scope.timeFormat = iAttrs.timeFormat || 'H a';
scope.dayLabelFormat= iAttrs.dayLabelFormat || (scope.startOfWeek ? 'dddd' : 'dddd, t\\he Do of MMMM');
scope.labelFormat = iAttrs.labelFormat || 'wo';
scope.offset = parseInt(iAttrs.offset, 10) || 0;
scope.now = moment();
scope.showHours = scope.numberOfWeeks === 1;
scope.date = moment(scope.controlDate);
scope.weeks = [];
// watch for changes in the control-date
scope.$watch(function() {
return scope.controlDate && scope.date.diff(moment(scope.controlDate));
}, function() {
// if we have some changes, change the internal date and update the calendar with the new data
scope.date = moment(scope.controlDate).add('days', scope.offset);
update();
});
scope.$watch(scope.events, update);
scope.$on('update', function() {
scope.$broadcast('calendar-update');
});
var update = function() {
// get us a copy of the date to work with
var date = scope.date.clone();
// if the calendar should start at the start of the week:
if(scope.startOfWeek) {
scope.date = getStartOfWeek(scope.date);
}
if(scope.numberOfWeeks === 1) {
scope.weeks = buildArray(scope.numberOfWeeks).map(function(week) {
return buildArray(scope.numberOfDays).map(function(day) {
return scope.date.clone().add('weeks', week).add('days', day + scope.offset);
})
})
} else {
scope.weeks = buildArray(scope.numberOfWeeks).map(function(week) {
return buildArray(scope.numberOfDays).map(function(day) {
return scope.date.clone().add('weeks', week + scope.offset).add('days', day);
})
})
}
// create the list of days that will be shown
scope.days = buildArray(scope.numberOfDays).map(function(index) {
return scope.date.clone().add('days', index);
});
var end = scope.days[scope.numberOfDays - 1].clone().eod();
// create a element for each hour of the day
scope.hours = buildArray(24).map(function(index) {
return scope.days[0].clone().sod().add('hours', index);
});
scope.$broadcast('calendar-update');
};
}
};
}]);
var app = angular.module('myApp', ['components', 'directive.calendar']);
var etag = 'etag', string = 'string', boolean = 'boolean', time = 'time', integer = 'integer', date = 'date',
datetime = 'datetime', attendee = 'attendee';
app.controller('Ctrl', ['$scope', 'eventService', function($scope, eventService) {
console.log('------------------------------------------------------');
console.log('-----------new run through the program----------------');
console.log('------------------------------------------------------');
$scope.events = [];
$scope.times = [];
$scope.date = moment().add('days', 0);
$scope.initForm = function() {
for (var i = 0; i < 96; i++) {
var date = moment().sod().add('minutes', i*15);
$scope.times.push(date);
}
};
$scope.addAttendee = function() {
$scope.calendar = $scope.calendar || {};
$scope.calendar.attendees = $scope.calendar.attendees || [];
$scope.calendar.attendees.push({
"id": "3m32ic0",
"email": $scope.calendar.attendee,
"displayName": "John",
"organizer": true,
"self": false,
"resource": false,
"optional": true,
"responseStatus": "none",
"comment": "none",
"additionalGuests": 0
});
$scope.calendar.attendee = '';
console.log('add attendee was clicked');
};
$scope.removeAttendee = function(attendee) {
var index = $scope.calendar.attendees.indexOf(attendee);
$scope.calendar.attendees.splice(index, 1);
console.log('remove attendee was clicked');
};
$scope.addEvent = function() {
console.log('add event was clicked');
};
var colorArray = ['blue', 'lilac', 'turqoise', 'green', 'bold-green', 'yellow',
'orange', 'red', 'bold-red', 'purple', 'grey'];
var createEvents = function(date) {
var d = date.getDate(),
m = date.getMonth(),
y = date.getFullYear(),
max = colorArray.length - 1,
min = 0,
color;
var randColor = function() {
return colorArray[Math.floor(Math.random() * (max - min + 1)) + min];
};
var ids = 11413,
s, e;
var id = function id() {
return ++ids;
};
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 4; j++) {
var temporaryEvent = {
"id" : id(),
"start" : s = moment().add('days', -i*j),
"end" : e = moment().add('days', j),
"colorId" : randColor(),
"summary" : "Event (" + (i*j+j+1) + " Days) Start: " + s.format('DD.MM') + " End: " + e.format('DD.MM'),
"description" : "Start: " + s + " End: " + e
};
$scope.events.push(temporaryEvent);
}
}
for (var i = -2; i < 2; i++) {
for (var j = 0; j < 4; j++) {
var rand = Math.floor(Math.random()*10)+1;
var inDayEvent = {
"id" : id(),
"start" : s = moment().add('days', i).sod().add('hours', rand),
"end" : e = moment().add('days', i).sod().add('hours', j + rand),
"colorId" : randColor(),
"summary" : "Event (" + (i*j+j+1) + " Days) Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM'),
"description" : "Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM')
};
$scope.events.push(inDayEvent);
}
}
var fiveDayEvent = {
"id" : id(),
"start" : s = moment().add('days', -2),
"end" : e = moment().add('days', 3),
"colorId" : randColor(),
"summary" : "Event (5 Days) Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM'),
"description" : "Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM')
};
$scope.events.push(fiveDayEvent);
var threeDayEvent = {
"id" : id(),
"start" : s = moment().add('days', -1),
"end" : e = moment().add('days', 2),
"colorId" : randColor(),
"summary" : "Event (3 Days) Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM'),
"description" : "Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM')
};
$scope.events.push(threeDayEvent);
var currentWholeDayEvent = {
"id" : id(),
"start" : s = moment().sod(),
"end" : e = moment().eod(),
"colorId" : randColor(),
"summary" : "Event (1 Days) Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM'),
"description" : "Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM')
};
$scope.events.push(currentWholeDayEvent);
var inDayEvent = {
"id" : id(),
"start" : s = moment().add('hours', -4),
"end" : e = moment().add('hours', 2),
"colorId" : randColor(),
"summary" : "Event (1 Days) Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM'),
"description" : "Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM')
};
$scope.events.push(inDayEvent);
var shortInDayEvent = {
"id" : id(),
"start" : s = moment().add('hours', -1),
"end" : e = moment().add('hours', 1),
"colorId" : randColor(),
"summary" : "Event (1 Days) Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM'),
"description" : "Start: " + s.format('DD.MM-HH:MM') + " End: " + e.format('DD.MM-HH:MM')
};
$scope.events.push(shortInDayEvent);
};
createEvents(new Date());
eventService.appendEvents($scope.events);
}]);
/** Table Builder here we go **/
.bs-calendar {
table-layout: fixed;
}
.bs-calendar-head {
margin-bottom: 5px;
}
.bs-calendar-tg-hours-head {
width: 30px;
text-align: center;
}
.bs-calendar-header tr td {
line-height: 20px;
padding: 0;
margin: 0;
}
.bs-calendar tbody tr.bs-calendar-header td {
padding: 8px;
background: #FFF;
}
.bs-calendar-header td .bs-calendar-daylabel {
border-bottom: 1px dotted #eee;
border-top: 1px dotted #eee;
margin: 0 -8px 0 -8px;
text-align: center;
}
.bs-calendar-weeknumber {
width: 29px;
}
.bs-calendar-weeknumber div {
padding: 0 4px 0px 10px;
margin: 0px -8px 0 0;
float: right;
border-top: 1px dotted #EEE;
border-bottom: 1px dotted #EEE;
border-left: 1px dotted #EEE;
border-radius: 4px 0 0 4px;
}
.bs-calendar tbody tr td {
padding: 0 5px 0 0;
font-size: 10px;
font-weight: bold;
text-align: right;
vertical-align: bottom;
}
.bs-calendar-tg-day {
position: relative;
height: 1030px;
}
.bs-calendar .today {
background-color: #F1F1F1;
}
.bs-tg-now {
position: relative;
left: 0;
width: 100%;
top: 0;
height: 0;
border-top: 2px solid #FF7F6E;
overflow: hidden;
z-index: 3;
}
.bs-calendar-colwrapper {
position: relative;
}
.bs-calendar-spanningwrapper {
margin: 1px 0 0 1px;
position: absolute;
width: 100%;
top: -1px;
z-index: 1;
}
.bs-calendar th {
border-bottom: 1px solid #DDD;
}
.bs-calendar-tg-markercell {
height: 42px;
border-top: 1px solid #DDD;
}
.bs-calendar-tg-day-container {
position: relative;
}
.bs-calendar-header {
border-top: 1px solid #DDD;
border-left: 1px solid #DDD;
border-right: 1px solid #DDD;
}
.bs-calendar-whole-day-event-list {
border-right: 1px solid #DDD;
}
.bs-calendar-whole-day-event-list td:first-child {
border-top: 0;
}
.bs-calendar-whole-day-event-container {
opacity: .7;
width: auto;
padding: 2px;
height: 17px;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-webkit-text-overflow: ellipsis;
}
.bs-calendar-whole-day-event-container-inner {
height: 17px;
overflow: hidden;
border-radius: 2px;
line-height: 14px;
}
.bs-calendar-whole-day-event-content {
text-align: left;
font-size: 1.1em;
word-wrap: break-word;
font-weight: bold;
padding: 2px 4px 2px 4px;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-webkit-text-overflow: ellipsis;
white-space: nowrap;
}
.bs-calendar-whole-day-event-container.hovered,
.bs-calendar-whole-day-event-container:hover {
opacity: .9;
overflow: inherit;
z-index: 9999;
position: relative;
}
.bs-calendar-whole-day-event-container.hovered .bs-calendar-whole-day-event-container-inner,
.bs-calendar-whole-day-event-container:hover .bs-calendar-whole-day-event-container-inner {
height: auto;
overflow: inherit;
}
.bs-calendar-whole-day-event-container.hovered .bs-calendar-whole-day-event-content,
.bs-calendar-whole-day-event-container:hover .bs-calendar-whole-day-event-content {
white-space: normal;
}
.bs-calendar-whole-day-space {
border-left: 1px solid #DDD;
border-right: 1px solid #DDD;
border-bottom: 1px dotted #DDD;
}
/* whole-day events end */
.bs-calendar-tg-hour-clock {
border-bottom: 1px dotted #DDD;
border-left: 1px dotted #DDD;
border-radius: 0 0 0 4px;
width: 40px;
height: 21px;
margin-right: -2px;
float: right;
padding-right: 4px;
}
.bs-calendar-tg-dualmarker {
height: 21px;
border-bottom: 1px dotted #DDD;
font-size: 1px;
}
.bs-calendar-tg-hours {
border-left: 1px solid #DDD;
background-color: white;
color: #555;
padding: 1px 0 0;
text-align: right;
vertical-align: top;
padding: 8px;
width: 80px;
}
.bs-calendar-tg-hour-inner {
padding-right: 2px;
height: 43px;
}
.bs-calendar-event-container {
opacity: .7;
width: 70%;
overflow: hidden;
z-index: 2;
height: 25px;
position: absolute;
}
.bs-calendar-event-content {
text-align: left;
min-height: 100%;
height: 100%;
word-wrap: break-word;
font-weight: normal;
border: 1px solid #CCC;
border: 1px solid rgba(0, 0, 0, 0.2);
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
-webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 2px 3px rgba(0, 0, 0, 0.2);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
}
.bs-calendar-event-header {
font-size: 1.2em;
font-weight: bold;
line-height: 1.2em;
padding: 1px 2px 0 2px;
border-radius: 2px;
background-color: rgba(0, 0, 0, 0.5);
color: rgb(255, 255, 255);
margin: 2px;
text-align: left;
word-wrap: break-word;
font-weight: bold;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-webkit-text-overflow: ellipsis;
white-space: nowrap;
}
.bs-calendar-event-body {
font-size: 1.1em;
margin: 4px;
}
.bs-calendar-event-container.hovered,
.bs-calendar-event-container:hover {
opacity: .9;
overflow: inherit;
z-index: 1000;
}
.bs-calendar-event-container.hovered .bs-calendar-event-content,
.bs-calendar-event-container:hover .bs-calendar-event-content {
height: auto;
}
.bs-calendar-event-container.hovered .bs-calendar-event-header,
.bs-calendar-event-container:hover .bs-calendar-event-header,
.bs-calendar-event-container.hovered .bs-calendar-event-body,
.bs-calendar-event-container:hover .bs-calendar-event-body {
white-space: normal;
}
/** Month view properties **/
.bs-calendar-month-row {
height: 120px;
}
angular.module('components', []).
directive('tabs', function() {
return {
restrict: 'E',
transclude: true,
controller: function($scope, $element, $timeout) {
var panes = $scope.panes = [];
var current = null;
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
current = pane;
$timeout(function() {
$scope.$broadcast('calendar-update');
}, 1);
};
this.addPane = function(pane) {
if (panes.length === 0) $scope.select(pane);
if (pane.paneDefault) $scope.select(pane);
panes.push(pane);
};
$scope.prev = function() {
current.prev();
}
$scope.next = function() {
current.next();
}
},
template:
'<div class="row">' +
'<div class="row" style="padding-bottom: 20px;">' +
'<div class="span5 btn-group">' +
'<button class="btn btn-primary" ng-click="prev()">Prev</button>' +
'<button class="btn btn-primary" ng-click="next()">Next</button>' +
'</div>' +
'<div class="span2">' +
'<h4>{{date.format("MMMM YYYY")}}</h4>' +
'</div>' +
'<div class="span5 btn-group" style="text-align: right">' +
'<button ng-repeat="pane in panes" class="btn" ng-class="{active:pane.selected}" ng-click="select(pane)">{{pane.paneTitle}}</button>' +
'</div>' +
'</div>' +
'<div class="row">' +
'<div class="tab-content" ng-transclude></div>' +
'</div>' +
'</div>',
replace: true
};
}).
directive('pane', [function() {
return {
require: '^tabs',
restrict: 'E',
transclude: true,
template:
'<div class="tab-pane" style="height: {{paneHeight}}px" ng-class="{active: selected}" ng-transclude>' +
'</div>',
replace: true,
scope: {
paneTitle: '@',
paneDefault: '@',
panePrev: '@',
paneNext: '@'
},
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
var docHeight,
panePos,
paneHeight;
panePos = angular.element('.tab-content').position();
docHeight = $(window).height();
paneHeight = docHeight - panePos.top - 20;
scope.paneHeight = paneHeight;
$scope = scope.$parent;
scope.prev = attrs.panePrev ? function() {
$scope.$eval(attrs.panePrev);
} : angular.noop;
scope.next = attrs.paneNext ? function() {
$scope.$eval(attrs.paneNext);
} : angular.noop;
angular.element(window).bind('resize', function() {
panePos = angular.element('.tab-content').position();
docHeight = $(window).height();
paneHeight = docHeight - panePos.top - 20;
scope.paneHeight = paneHeight;
console.log(panePos, docHeight, paneHeight);
scope.$digest();
});
}
};
}]);
body {
height: 100%;
}
/* Colordefinitions .color-{{colorId}} */
.color-blue {
background-color: #5484ED;
border-color: #5484ED;
}
.color-lilac {
background-color: #A4BDFC;
border-color: #A4BDFC;
}
.color-turqoise {
background-color: #46D6DB;
border-color: #46D6DB;
}
.color-green {
background-color: #7AE7BF;
border-color: #7AE7BF;
}
.color-bold-green {
background-color: #51B749;
border-color: #51B749;
}
.color-yellow {
background-color: #FBD75B;
border-color: #FBD75B;
}
.color-orange {
background-color: #FFB878;
border-color: #FFB878;
}
.color-red {
background-color: #FF887C;
border-color: #FF887C;
}
.color-bold-red {
background-color: #DC2127;
border-color: #DC2127;
}
.color-purple {
background-color: #DBADFF;
border-color: #DBADFF;
}
.color-grey {
background-color: #E1E1E1;
border-color: #E1E1E1;
}
<div class="row" style="padding-top: 40px" ng-controller="CalendarController">
<div class="span12" style="padding-bottom: 20px">
<a href="#/components/calendar" class="btn btn-warning pull-left">Return to calendar</a>
</div>
<div class="span12 form-bg">
<form class="form-horizontal" ng-submit="addEvent()" ng-init="initForm()" name="create">
<h3><i class="icon-pencil"></i> Add event</h3>
<fieldset>
<div class="control-group">
<div class="controls">
<div class="input-prepend">
<input placeholder="Summary..." type="text" class="input-xxlarge" ng-model="calendar.summary" name="summary" />
</div>
<span ng-show="form.summary.$error" class="help-inline">{{errors.summary}}</span>
</div>
</div>
<div class="control-group">
<div class="controls">
<div class="input-prepend">
<input placeholder="Start date..." type="text" class="input-small" style="margin-right: 3px;" ng-model="calendar.start.date" name="start.date" />
<select placeholder="Start time" ng-model="calendar.start.dateTime" class="input-small">
<option ng-repeat="time in times">{{time.format("hh:mm")}}</option>
</select>
<input placeholder="End date..." type="text" class="input-small" style="margin-right: 3px;" ng-model="calendar.end.date" name="end.date" />
<select placeholder="End time" ng-model="calendar.end.dateTime" class="input-small">
<option ng-repeat="time in times">{{time.format("hh:mm")}}</option>
</select>
</div>
<span ng-show="form.summary.$error" class="help-inline">{{errors.summary}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="surrname">Where:</label>
<div class="controls">
<div class="input-prepend">
<input placeholder="Where..." type="text" class="input-xlarge" ng-model="calendaer.location" name="location" />
</div>
<span ng-show="form.location.$error" class="help-inline">{{errors.location}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="name">Calendar:</label>
<div class="controls">
<div class="input-prepend">
<select name="id" ng-model="calendar.id">
<option>Calendar 1</option>
<option>Calendar 2</option>
</select>
</div>
<span ng-show="form.id.$error" class="help-inline">{{errors.id}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="name">Description:</label>
<div class="controls">
<div class="input-prepend">
<textarea placeholder="Description..." type="text" class="input-xlarge" ng-model="calendar.description" name="description">
</textarea>
</div>
<span ng-show="form.description.$error" class="help-inline">{{errors.description}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="colors">Event color:</label>
<div class="controls">
<div class="input-prepend">
<ng-color-picker model-object="calendar" model-property="color"></ng-color-picker>
</div>
</div>
</div>
<div class="control-group">
<label class="control-label" for="reminders">Reminder:</label>
<div class="controls">
<div class="input-prepend">
<select name="reminders" class="input-small" ng-model="calendar.reminders.method">
<option>E-Mail</option>
<option>Popup</option>
</select>
<input placeholder="10" type="text" class="input-mini" ng-model="calendar.reminders.minutes" name="minutes" />
<select name="timespan" class="input-small" ng-model="calendar.reminders.length">
<option>minutes</option>
<option>hours</option>
<option>days</option>
</select>
</div>
<span ng-show="form.reminder.$error" class="help-inline">{{errors.reminder}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="availability">Show me as:</label>
<div class="controls">
<div class="input-prepend">
<label class="radio">
<input type="radio" name="optionsRadios" id="optionsRadios1" ng-model="calendar.locked" value="available" checked>
Available
</label>
<label class="radio">
<input type="radio" name="optionsRadios" id="optionsRadios2" ng-model="calendar.locked" value="busy">
Busy
</label>
</div>
<span ng-show="form.locked.$error" class="help-inline">{{errors.locked}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="confirmPassword">Privacy:</label>
<div class="controls">
<div class="input-prepend">
<label class="radio">
<input type="radio" name="optionsRadios2" id="optionsRadios3" ng-model="calendar.visibility" value="default" checked>
Default
</label>
<label class="radio">
<input type="radio" name="optionsRadios2" id="optionsRadios4" ng-model="calendar.visibility" value="public">
Public
</label>
<label class="radio">
<input type="radio" name="optionsRadios2" id="optionsRadios5" ng-model="calendar.visibility" value="private">
Private
</label>
</div>
<span ng-show="form.visibility.$error" class="help-inline">{{errors.visibility}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="guest">Add guests:</label>
<div class="controls">
<div class="input-prepend">
<span class="add-on"><i class="icon-user"></i></span>
<input placeholder="Enter invitee..." type="text" class="input-large" ng-model="calendar.attendee" name="attendee" />
<button class="btn btn-primary" ng-click="addAttendee()">Add</button>
</div>
<span ng-show="form.attendee.$error" class="help-inline">{{errors.attendee}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="invitees"> </label>
<div class="controls">
<ul class="unstyled">
<li ng-repeat="attendee in calendar.attendees">
<span>{{attendee.email}}</span>
<a href="" ng-click="removeAttendee(attendee)"><i class="icon-remove"></i></a>
</li>
</ul>
</div>
</div>
<div class="control-group">
<label class="control-label" for="surrname">Guests may:</label>
<div class="controls">
<label class="checkbox">
<input type="checkbox" ng-model="calendar.guestsCanModify"> modify event
</label>
<label class="checkbox">
<input type="checkbox" ng-model="calendar.guestsCanInviteOthers"> invite others
</label>
<label class="checkbox">
<input type="checkbox" ng-model="calendar.guestsCanSeeOtherGuests"> see guest list
</label>
</div>
</div>
</fieldset>
<div class="form-actions">
<button class="btn" ng-click="reset()" ng-disabled="isUnchanged(calendar)">Cancel</button>
<button class="btn btn-primary" type="submit">Save</button>
</div>
</form>
</div>
<div class="span12">
<pre>calendar = {{calendar | json}}</pre>
</div>
</div>
<form class="form-horizontal" ng-submit="addEvent()" name="form">
<div class="span9">
<h3><i class="icon-pencil"></i> Add event</h3>
<fieldset>
<div class="control-group">
<label class="control-label" for="name">When:</label>
<div class="controls">
Thu, November 8, 6:30am - 7.30am
</div>
</div>
<div class="control-group">
<label class="control-label" for="name">What:</label>
<div class="controls">
<div class="input-prepend">
<!--<span class="add-on"><i class="icon-user"></i></span>-->
<input placeholder="Summary..." type="text" class="input-xlarge" ng-model="calendar.summary" name="summary" />
</div>
<span ng-show="form.calendar.$error" class="help-inline">{{errors.calendar}}</span>
</div>
</div>
<div class="control-group">
<label class="control-label" for="name">Calendar:</label>
<div class="controls">
<div class="input-prepend">
<!--<span class="add-on"><i class="icon-user"></i></span>-->
<select name="cpu" ng-model="event.calendar">
<option>Calendar 1</option>
<option>Calendar 2</option>
</select>
</div>
<span ng-show="form.calendar.$error" class="help-inline">{{errors.calendar}}</span>
</div>
</div>
</fieldset>
<div class="form-actions">
<button class="btn" ng-click="reset()" ng-disabled="isUnchanged(calendar)">Cancel</button>
<button class="btn btn-primary" type="submit">Save</button>
<button class="btn btn-inverse" type="submit">Edit</button>
</div>
</div>
</form>
calendar = {
"kind": "calendar#event",
"etag": etag,
"id": string,
"created": new Date(),
"updated": new Date(),
"summary": "This is the summary",
"description": "This is the description",
"location": "This is the location",
"visibility": "default",
"colorId": "red",
"creator": {
"id": "m8doslak",
"email": "a@b.com",
"displayName": "Lordnox",
"self": true
},
"organizer": {
"id": "m8doslak",
"email": "a@b.com",
"displayName": "Lordnox",
"self": true
},
"start": {
"date": new Date(),
"dateTime": new Date(),
"timeZone": "CET"
},
"end": {
"date": new Date(),
"dateTime": new Date(),
"timeZone": "CET"
},
"endTimeUnspecified": false,
"recurrence": "no",
"recurringEventId": "",
"originalStartTime": {
"date": new Date(),
"dateTime": new Date(),
"timeZone": "CET"
},
"iCalUID": "282mmn23nc923",
"sequence": 0,
"attendees": [{
"id": "m8doslak",
"email": "a@b.com",
"displayName": "Lordnox",
"self": true,
"organizer": true,
"resource": false,
"optional": true,
"responseStatus": "none",
"comment": "(bow)",
"additionalGuests": 0}
],
"attendeesOmitted": false,
"anyoneCanAddSelf": boolean,
"guestsCanInviteOthers": boolean,
"guestsCanModify": boolean,
"guestsCanSeeOtherGuests": boolean,
"locked": true,
"reminders": {
"useDefault": true,
"overrides": [
{
"method": "E-Mail",
"minutes": 15
}
]
}
}