<!DOCTYPE html>
<html ng-app="app">
<head>
<link data-require="fullcalendar@*" data-semver="2.3.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.3.0/fullcalendar.min.css" />
<link data-require="bootstrap-css@*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<link data-require="jqueryui@*" data-semver="1.10.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/css/smoothness/jquery-ui-1.10.0.custom.min.css" />
<script data-require="jquery@*" data-semver="1.11.2" src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
<script data-require="jqueryui@*" data-semver="1.10.0" src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/jquery-ui.js"></script>
<script data-require="moment.js@*" data-semver="2.8.3" src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.8.3/moment.min.js"></script>
<script data-require="angular.js@1.2.28" data-semver="1.2.28" src="https://code.angularjs.org/1.2.28/angular.js"></script>
<script data-require="fullcalendar@*" data-semver="2.3.0" src="//cdnjs.cloudflare.com/ajax/libs/fullcalendar/2.3.0/fullcalendar.min.js"></script>
<script data-require="ui-bootstrap@*" data-semver="0.12.1" src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.12.1.min.js"></script>
<script src="uicalendar.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div class="container" style="padding-top: 40px" ng-controller="mainCtrl">
<div id="ApptsCalendar" calendar="ApptsCalendar" ui-calendar="calendarOptions.calendar" ng-model="Events"></div>
</div>
</body>
</html>
var app = angular.module('app', ['ui.bootstrap', 'ui.calendar']);
app.controller('mainCtrl', function($scope) {
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
$scope.Events = [{
title: 'All Day Event',
start: new Date(y, m, 1)
}, {
title: 'Long Event',
start: new Date(y, m, d - 5),
end: new Date(y, m, d - 2)
}, {
id: 999,
title: 'Repeating Event',
start: new Date(y, m, d - 3, 16, 0),
allDay: false
}, {
id: 999,
title: 'Repeating Event',
start: new Date(y, m, d + 4, 16, 0),
allDay: false
}, {
title: 'Birthday Party',
start: new Date(y, m, d + 1, 19, 0),
end: new Date(y, m, d + 1, 22, 30),
allDay: false
}];
$scope.loadPopover = function(type, element) {
var templateUrl, title;
if (type == 'details') {
templateUrl = "details.html";
title = "Appointment Details";
} else if (type == 'new') {
templateUrl = "new.html";
title = "New Appointment";
}
//var myPopover = $popover(element, {
// title: "New Appointment",
// show: true,
// autoClose: true,
// placement: "auto",
// container: "body",
// template: templateUrl,
// scope: $scope.$new()
//});
}
var onEventClick = function(event, jsEvent, view) {
$scope.loadPopover("details", $(this));
};
var onDayClick = function(date, jsEvent, view) {
$scope.loadPopover("new", $(this));
};
$scope.calendarOptions = {
calendar: {
height: 600,
header: {
left: 'title',
center: '',
right: 'today prev,next'
},
eventClick: onEventClick,
dayClick: onDayClick
}
};
});
/* Styles go here */
/*
* AngularJs Fullcalendar Wrapper for the JQuery FullCalendar
* API @ http://arshaw.com/fullcalendar/
*
* Angular Calendar Directive that takes in the [eventSources] nested array object as the ng-model and watches it deeply changes.
* Can also take in multiple event urls as a source object(s) and feed the events per view.
* The calendar will watch any eventSource array and update itself when a change is made.
*
*/
angular.module('ui.calendar', [])
.constant('uiCalendarConfig', {calendars: {}})
.controller('uiCalendarCtrl', ['$scope',
'$timeout',
'$locale', function(
$scope,
$timeout,
$locale){
var sourceSerialId = 1,
eventSerialId = 1,
sources = $scope.eventSources,
extraEventSignature = $scope.calendarWatchEvent ? $scope.calendarWatchEvent : angular.noop,
wrapFunctionWithScopeApply = function(functionToWrap){
var wrapper;
if (functionToWrap){
wrapper = function(){
// This happens outside of angular context so we need to wrap it in a timeout which has an implied apply.
// In this way the function will be safely executed on the next digest.
var args = arguments;
var _this = this;
$timeout(function(){
functionToWrap.apply(_this, args);
});
};
}
return wrapper;
};
this.eventsFingerprint = function(e) {
if (!e._id) {
e._id = eventSerialId++;
}
// This extracts all the information we need from the event. http://jsperf.com/angular-calendar-events-fingerprint/3
return "" + e._id + (e.id || '') + (e.title || '') + (e.url || '') + (+e.start || '') + (+e.end || '') +
(e.allDay || '') + (e.className || '') + extraEventSignature(e) || '';
};
this.sourcesFingerprint = function(source) {
return source.__id || (source.__id = sourceSerialId++);
};
this.allEvents = function() {
// return sources.flatten(); but we don't have flatten
var arraySources = [];
for (var i = 0, srcLen = sources.length; i < srcLen; i++) {
var source = sources[i];
if (angular.isArray(source)) {
// event source as array
arraySources.push(source);
} else if(angular.isObject(source) && angular.isArray(source.events)){
// event source as object, ie extended form
var extEvent = {};
for(var key in source){
if(key !== '_uiCalId' && key !== 'events'){
extEvent[key] = source[key];
}
}
for(var eI = 0;eI < source.events.length;eI++){
angular.extend(source.events[eI],extEvent);
}
arraySources.push(source.events);
}
}
return Array.prototype.concat.apply([], arraySources);
};
// Track changes in array by assigning id tokens to each element and watching the scope for changes in those tokens
// arguments:
// arraySource array of function that returns array of objects to watch
// tokenFn function(object) that returns the token for a given object
this.changeWatcher = function(arraySource, tokenFn) {
var self;
var getTokens = function() {
var array = angular.isFunction(arraySource) ? arraySource() : arraySource;
var result = [], token, el;
for (var i = 0, n = array.length; i < n; i++) {
el = array[i];
token = tokenFn(el);
map[token] = el;
result.push(token);
}
return result;
};
// returns elements in that are in a but not in b
// subtractAsSets([4, 5, 6], [4, 5, 7]) => [6]
var subtractAsSets = function(a, b) {
var result = [], inB = {}, i, n;
for (i = 0, n = b.length; i < n; i++) {
inB[b[i]] = true;
}
for (i = 0, n = a.length; i < n; i++) {
if (!inB[a[i]]) {
result.push(a[i]);
}
}
return result;
};
// Map objects to tokens and vice-versa
var map = {};
var applyChanges = function(newTokens, oldTokens) {
var i, n, el, token;
var replacedTokens = {};
var removedTokens = subtractAsSets(oldTokens, newTokens);
for (i = 0, n = removedTokens.length; i < n; i++) {
var removedToken = removedTokens[i];
el = map[removedToken];
delete map[removedToken];
var newToken = tokenFn(el);
// if the element wasn't removed but simply got a new token, its old token will be different from the current one
if (newToken === removedToken) {
self.onRemoved(el);
} else {
replacedTokens[newToken] = removedToken;
self.onChanged(el);
}
}
var addedTokens = subtractAsSets(newTokens, oldTokens);
for (i = 0, n = addedTokens.length; i < n; i++) {
token = addedTokens[i];
el = map[token];
if (!replacedTokens[token]) {
self.onAdded(el);
}
}
};
return self = {
subscribe: function(scope, onChanged) {
scope.$watch(getTokens, function(newTokens, oldTokens) {
if (!onChanged || onChanged(newTokens, oldTokens) !== false) {
applyChanges(newTokens, oldTokens);
}
}, true);
},
onAdded: angular.noop,
onChanged: angular.noop,
onRemoved: angular.noop
};
};
this.getFullCalendarConfig = function(calendarSettings, uiCalendarConfig){
var config = {};
angular.extend(config, uiCalendarConfig);
angular.extend(config, calendarSettings);
angular.forEach(config, function(value,key){
if (typeof value === 'function'){
config[key] = wrapFunctionWithScopeApply(config[key]);
}
});
return config;
};
this.getLocaleConfig = function(fullCalendarConfig) {
if (!fullCalendarConfig.lang || fullCalendarConfig.useNgLocale) {
// Configure to use locale names by default
var tValues = function(data) {
// convert {0: "Jan", 1: "Feb", ...} to ["Jan", "Feb", ...]
var r, k;
r = [];
for (k in data) {
r[k] = data[k];
}
return r;
};
var dtf = $locale.DATETIME_FORMATS;
return {
monthNames: tValues(dtf.MONTH),
monthNamesShort: tValues(dtf.SHORTMONTH),
dayNames: tValues(dtf.DAY),
dayNamesShort: tValues(dtf.SHORTDAY)
};
}
return {};
};
}])
.directive('uiCalendar', ['uiCalendarConfig', function(uiCalendarConfig) {
return {
restrict: 'A',
scope: {eventSources:'=ngModel',calendarWatchEvent: '&'},
controller: 'uiCalendarCtrl',
link: function(scope, elm, attrs, controller) {
var sources = scope.eventSources,
sourcesChanged = false,
calendar,
eventSourcesWatcher = controller.changeWatcher(sources, controller.sourcesFingerprint),
eventsWatcher = controller.changeWatcher(controller.allEvents, controller.eventsFingerprint),
options = null;
function getOptions(){
var calendarSettings = attrs.uiCalendar ? scope.$parent.$eval(attrs.uiCalendar) : {},
fullCalendarConfig;
fullCalendarConfig = controller.getFullCalendarConfig(calendarSettings, uiCalendarConfig);
var localeFullCalendarConfig = controller.getLocaleConfig(fullCalendarConfig);
angular.extend(localeFullCalendarConfig, fullCalendarConfig);
options = { eventSources: sources };
angular.extend(options, localeFullCalendarConfig);
//remove calendars from options
options.calendars = null;
var options2 = {};
for(var o in options){
if(o !== 'eventSources'){
options2[o] = options[o];
}
}
return JSON.stringify(options2);
}
scope.destroy = function(){
if(calendar && calendar.fullCalendar){
calendar.fullCalendar('destroy');
}
if(attrs.calendar) {
calendar = uiCalendarConfig.calendars[attrs.calendar] = $(elm).html('');
} else {
calendar = $(elm).html('');
}
};
scope.init = function(){
calendar.fullCalendar(options);
};
eventSourcesWatcher.onAdded = function(source) {
calendar.fullCalendar('addEventSource', source);
sourcesChanged = true;
};
eventSourcesWatcher.onRemoved = function(source) {
calendar.fullCalendar('removeEventSource', source);
sourcesChanged = true;
};
eventsWatcher.onAdded = function(event) {
calendar.fullCalendar('renderEvent', event);
};
eventsWatcher.onRemoved = function(event) {
calendar.fullCalendar('removeEvents', function(e) {
return e._id === event._id;
});
};
eventsWatcher.onChanged = function(event) {
event._start = $.fullCalendar.moment(event.start);
event._end = $.fullCalendar.moment(event.end);
calendar.fullCalendar('updateEvent', event);
};
eventSourcesWatcher.subscribe(scope);
eventsWatcher.subscribe(scope, function(newTokens, oldTokens) {
if (sourcesChanged === true) {
sourcesChanged = false;
// prevent incremental updates in this case
return false;
}
});
scope.$watch(getOptions, function(newO,oldO){
scope.destroy();
scope.init();
});
}
};
}]);
<div class="popover" tabindex="-1">
<div class="arrow"></div>
<h3 class="popover-title">Appointment Details</h3>
<div class="popover-content">
<div class="row">
<div class="col-md-3"><label class="control-label">Office:</label></div>
<div class="col-md-9">{{Model.SelectedEvent.office.name}}</div>
</div>
<div class="row">
<div class="col-md-3"><label class="control-label">Start Date:</label></div>
<div class="col-md-9">{{Model.SelectedEvent.start.format("MM/DD/YYYY") | date: 'MM/dd/yyyy'}}</div>
</div>
<div class="row">
<div class="col-md-3"><label class="control-label">End Date:</label></div>
<div class="col-md-9">{{Model.SelectedEvent.end.format("MM/DD/YYYY") | date: 'MM/dd/yyyy'}}</div>
</div>
<h4>Patient Information</h4>
<div class="row">
<div class="col-md-3"><label class="control-label">MRN:</label></div>
<div class="col-md-9">{{Model.SelectedEvent.person.mrn}}</div>
</div>
<div class="row">
<div class="col-md-3"><label class="control-label">Name:</label></div>
<div class="col-md-9">{{Model.SelectedEvent.person.name}}</div>
</div>
<div class="row">
<div class="col-md-3"><label class="control-label">DOB:</label></div>
<div class="col-md-9">{{Model.SelectedEvent.person.dob | date: 'MM/dd/yyyy'}}</div>
</div>
<div class="row">
<div class="col-md-3"><label class="control-label">Address:</label></div>
<div class="col-md-9">{{Model.SelectedEvent.person.address}}</div>
</div>
<div class="row">
<div class="col-md-3"><label class="control-label">Phone:</label></div>
<div class="col-md-9">{{Model.SelectedEvent.person.phone}}</div>
</div>
<hr />
<div class="form-group btn-group pull-right">
<button type="button" class="btn btn-danger" ng-click="DeleteAppointment();$hide();"><i class="fa fa-trash"></i> Remove</button>
<button type="button" class="btn btn-default" ng-click="openEditAppointment();$hide()"><i class="fa fa-edit"></i> Edit</button>
</div>
</div>
</div>
<div class="popover" tabindex="-1">
<div class="arrow"></div>
<h3 class="popover-title">New Appointment</h3>
<div class="popover-content">
<form name="newApptForm" novalidate>
<div class="form-group" >
<label class="control-label">Device</label>
<select class="form-control" name="devices" >
<option value="">Select Device</option>
</select>
</div>
<div class="form-group" >
<label class="control-label">Office</label>
<select class="form-control" name="offices" >
<option value="">Select Office</option>
</select>
</div>
<hr />
<div class="form-group pull-right">
<button type="button" class="btn btn-primary" ng-disabled="newApptForm.$invalid" ng-click="SaveAppointment();$hide()"><i class="fa fa-save"></i> Create Appointment</button>
</div>
</form>
</div>
</div>