jquery-week-calendar 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Adam Fortuna
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,17 @@
1
+ Wrapper for the jQuery-Week-Calendar Javascrip library.
2
+
3
+ https://github.com/themouette/jquery-week-calendar
4
+
5
+ Just add to your gemfile:
6
+
7
+ gem 'jquery-week-calendar'
8
+
9
+ And include in your application.js:
10
+
11
+ //= require jquery-week-calendar
12
+
13
+ Optionall include in your application.css
14
+
15
+ *= require jquery-week-calendar
16
+
17
+ You'll probably want to also use jquery-ui with it for a quick skin. For this I used the 'jquery-ui-themes' gem to get setup, but you can also use the Google hosted version of course.
@@ -0,0 +1,11 @@
1
+ require 'rails'
2
+
3
+ module JqueryWeekCalendar
4
+ module Rails
5
+ if ::Rails.version < "3.1"
6
+ require 'jquery-week-calendar/railtie'
7
+ else
8
+ require 'jquery-week-calendar/engine'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ require 'rails'
2
+
3
+ module JqueryWeekCalendar
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ require 'rails/generators'
2
+
3
+ module JqueryWeekCalendar
4
+ class Install < ::Rails::Generators::Base
5
+ JAVASCRIPTS = File.expand_path('../../../vendor/assets/javascripts', __FILE__)
6
+ STYLESHEETS = File.expand_path('../../../vendor/assets/stylesheets', __FILE__)
7
+
8
+ def self.source_root
9
+ @source_root ||= JAVASCRIPTS
10
+ end
11
+
12
+ def copy_calendar
13
+ Dir[File.join(JAVASCRIPTS, '*.js')].each do |file|
14
+ file = File.split(file).last
15
+ copy_file file, "public/javascripts/#{file}"
16
+ end
17
+
18
+ Dir[File.join(STYLESHEETS, '*.css')].each do |file|
19
+ file = File.split(file).last
20
+ copy_file file, "public/stylesheets/#{file}"
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,11 @@
1
+ require 'rails'
2
+
3
+ module JqueryWeekCalendar
4
+ module Rails
5
+ class Railtie < ::Rails::Railtie
6
+ generators do
7
+ require 'jquery-week-calendar/generators'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,3011 @@
1
+ /**
2
+ * @version: 1.0 Alpha-1
3
+ * @author: Coolite Inc. http://www.coolite.com/
4
+ * @date: 2008-05-13
5
+ * @copyright: Copyright (c) 2006-2008, Coolite Inc. (http://www.coolite.com/). All rights reserved.
6
+ * @license: Licensed under The MIT License. See license.txt and http://www.datejs.com/license/.
7
+ * @website: http://www.datejs.com/
8
+ */
9
+ Date.CultureInfo={name:"en-US",englishName:"English (United States)",nativeName:"English (United States)",dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],abbreviatedDayNames:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],shortestDayNames:["Su","Mo","Tu","We","Th","Fr","Sa"],firstLetterDayNames:["S","M","T","W","T","F","S"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],abbreviatedMonthNames:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],amDesignator:"AM",pmDesignator:"PM",firstDayOfWeek:0,twoDigitYearMax:2029,dateElementOrder:"mdy",formatPatterns:{shortDate:"M/d/yyyy",longDate:"dddd, MMMM dd, yyyy",shortTime:"h:mm tt",longTime:"h:mm:ss tt",fullDateTime:"dddd, MMMM dd, yyyy h:mm:ss tt",sortableDateTime:"yyyy-MM-ddTHH:mm:ss",universalSortableDateTime:"yyyy-MM-dd HH:mm:ssZ",rfc1123:"ddd, dd MMM yyyy HH:mm:ss GMT",monthDay:"MMMM dd",yearMonth:"MMMM, yyyy"},regexPatterns:{jan:/^jan(uary)?/i,feb:/^feb(ruary)?/i,mar:/^mar(ch)?/i,apr:/^apr(il)?/i,may:/^may/i,jun:/^jun(e)?/i,jul:/^jul(y)?/i,aug:/^aug(ust)?/i,sep:/^sep(t(ember)?)?/i,oct:/^oct(ober)?/i,nov:/^nov(ember)?/i,dec:/^dec(ember)?/i,sun:/^su(n(day)?)?/i,mon:/^mo(n(day)?)?/i,tue:/^tu(e(s(day)?)?)?/i,wed:/^we(d(nesday)?)?/i,thu:/^th(u(r(s(day)?)?)?)?/i,fri:/^fr(i(day)?)?/i,sat:/^sa(t(urday)?)?/i,future:/^next/i,past:/^last|past|prev(ious)?/i,add:/^(\+|aft(er)?|from|hence)/i,subtract:/^(\-|bef(ore)?|ago)/i,yesterday:/^yes(terday)?/i,today:/^t(od(ay)?)?/i,tomorrow:/^tom(orrow)?/i,now:/^n(ow)?/i,millisecond:/^ms|milli(second)?s?/i,second:/^sec(ond)?s?/i,minute:/^mn|min(ute)?s?/i,hour:/^h(our)?s?/i,week:/^w(eek)?s?/i,month:/^m(onth)?s?/i,day:/^d(ay)?s?/i,year:/^y(ear)?s?/i,shortMeridian:/^(a|p)/i,longMeridian:/^(a\.?m?\.?|p\.?m?\.?)/i,timezone:/^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,ordinalSuffix:/^\s*(st|nd|rd|th)/i,timeContext:/^\s*(\:|a(?!u|p)|p)/i},timezones:[{name:"UTC",offset:"-000"},{name:"GMT",offset:"-000"},{name:"EST",offset:"-0500"},{name:"EDT",offset:"-0400"},{name:"CST",offset:"-0600"},{name:"CDT",offset:"-0500"},{name:"MST",offset:"-0700"},{name:"MDT",offset:"-0600"},{name:"PST",offset:"-0800"},{name:"PDT",offset:"-0700"}]};
10
+ (function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,p=function(s,l){if(!l){l=2;}
11
+ return("000"+s).slice(l*-1);};$P.clearTime=function(){this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);return this;};$P.setTimeToNow=function(){var n=new Date();this.setHours(n.getHours());this.setMinutes(n.getMinutes());this.setSeconds(n.getSeconds());this.setMilliseconds(n.getMilliseconds());return this;};$D.today=function(){return new Date().clearTime();};$D.compare=function(date1,date2){if(isNaN(date1)||isNaN(date2)){throw new Error(date1+" - "+date2);}else if(date1 instanceof Date&&date2 instanceof Date){return(date1<date2)?-1:(date1>date2)?1:0;}else{throw new TypeError(date1+" - "+date2);}};$D.equals=function(date1,date2){return(date1.compareTo(date2)===0);};$D.getDayNumberFromName=function(name){var n=$C.dayNames,m=$C.abbreviatedDayNames,o=$C.shortestDayNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s||o[i].toLowerCase()==s){return i;}}
12
+ return-1;};$D.getMonthNumberFromName=function(name){var n=$C.monthNames,m=$C.abbreviatedMonthNames,s=name.toLowerCase();for(var i=0;i<n.length;i++){if(n[i].toLowerCase()==s||m[i].toLowerCase()==s){return i;}}
13
+ return-1;};$D.isLeapYear=function(year){return((year%4===0&&year%100!==0)||year%400===0);};$D.getDaysInMonth=function(year,month){return[31,($D.isLeapYear(year)?29:28),31,30,31,30,31,31,30,31,30,31][month];};$D.getTimezoneAbbreviation=function(offset){var z=$C.timezones,p;for(var i=0;i<z.length;i++){if(z[i].offset===offset){return z[i].name;}}
14
+ return null;};$D.getTimezoneOffset=function(name){var z=$C.timezones,p;for(var i=0;i<z.length;i++){if(z[i].name===name.toUpperCase()){return z[i].offset;}}
15
+ return null;};$P.clone=function(){return new Date(this.getTime());};$P.compareTo=function(date){return Date.compare(this,date);};$P.equals=function(date){return Date.equals(this,date||new Date());};$P.between=function(start,end){return this.getTime()>=start.getTime()&&this.getTime()<=end.getTime();};$P.isAfter=function(date){return this.compareTo(date||new Date())===1;};$P.isBefore=function(date){return(this.compareTo(date||new Date())===-1);};$P.isToday=function(){return this.isSameDay(new Date());};$P.isSameDay=function(date){return this.clone().clearTime().equals(date.clone().clearTime());};$P.addMilliseconds=function(value){this.setMilliseconds(this.getMilliseconds()+value);return this;};$P.addSeconds=function(value){return this.addMilliseconds(value*1000);};$P.addMinutes=function(value){return this.addMilliseconds(value*60000);};$P.addHours=function(value){return this.addMilliseconds(value*3600000);};$P.addDays=function(value){this.setDate(this.getDate()+value);return this;};$P.addWeeks=function(value){return this.addDays(value*7);};$P.addMonths=function(value){var n=this.getDate();this.setDate(1);this.setMonth(this.getMonth()+value);this.setDate(Math.min(n,$D.getDaysInMonth(this.getFullYear(),this.getMonth())));return this;};$P.addYears=function(value){return this.addMonths(value*12);};$P.add=function(config){if(typeof config=="number"){this._orient=config;return this;}
16
+ var x=config;if(x.milliseconds){this.addMilliseconds(x.milliseconds);}
17
+ if(x.seconds){this.addSeconds(x.seconds);}
18
+ if(x.minutes){this.addMinutes(x.minutes);}
19
+ if(x.hours){this.addHours(x.hours);}
20
+ if(x.weeks){this.addWeeks(x.weeks);}
21
+ if(x.months){this.addMonths(x.months);}
22
+ if(x.years){this.addYears(x.years);}
23
+ if(x.days){this.addDays(x.days);}
24
+ return this;};var $y,$m,$d;$P.getWeek=function(){var a,b,c,d,e,f,g,n,s,w;$y=(!$y)?this.getFullYear():$y;$m=(!$m)?this.getMonth()+1:$m;$d=(!$d)?this.getDate():$d;if($m<=2){a=$y-1;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=0;f=$d-1+(31*($m-1));}else{a=$y;b=(a/4|0)-(a/100|0)+(a/400|0);c=((a-1)/4|0)-((a-1)/100|0)+((a-1)/400|0);s=b-c;e=s+1;f=$d+((153*($m-3)+2)/5)+58+s;}
25
+ g=(a+b)%7;d=(f+g-e)%7;n=(f+3-d)|0;if(n<0){w=53-((g-s)/5|0);}else if(n>364+s){w=1;}else{w=(n/7|0)+1;}
26
+ $y=$m=$d=null;return w;};$P.getISOWeek=function(){$y=this.getUTCFullYear();$m=this.getUTCMonth()+1;$d=this.getUTCDate();return p(this.getWeek());};$P.setWeek=function(n){return this.moveToDayOfWeek(1).addWeeks(n-this.getWeek());};$D._validate=function(n,min,max,name){if(typeof n=="undefined"){return false;}else if(typeof n!="number"){throw new TypeError(n+" is not a Number.");}else if(n<min||n>max){throw new RangeError(n+" is not a valid value for "+name+".");}
27
+ return true;};$D.validateMillisecond=function(value){return $D._validate(value,0,999,"millisecond");};$D.validateSecond=function(value){return $D._validate(value,0,59,"second");};$D.validateMinute=function(value){return $D._validate(value,0,59,"minute");};$D.validateHour=function(value){return $D._validate(value,0,23,"hour");};$D.validateDay=function(value,year,month){return $D._validate(value,1,$D.getDaysInMonth(year,month),"day");};$D.validateMonth=function(value){return $D._validate(value,0,11,"month");};$D.validateYear=function(value){return $D._validate(value,0,9999,"year");};$P.set=function(config){if($D.validateMillisecond(config.millisecond)){this.addMilliseconds(config.millisecond-this.getMilliseconds());}
28
+ if($D.validateSecond(config.second)){this.addSeconds(config.second-this.getSeconds());}
29
+ if($D.validateMinute(config.minute)){this.addMinutes(config.minute-this.getMinutes());}
30
+ if($D.validateHour(config.hour)){this.addHours(config.hour-this.getHours());}
31
+ if($D.validateMonth(config.month)){this.addMonths(config.month-this.getMonth());}
32
+ if($D.validateYear(config.year)){this.addYears(config.year-this.getFullYear());}
33
+ if($D.validateDay(config.day,this.getFullYear(),this.getMonth())){this.addDays(config.day-this.getDate());}
34
+ if(config.timezone){this.setTimezone(config.timezone);}
35
+ if(config.timezoneOffset){this.setTimezoneOffset(config.timezoneOffset);}
36
+ if(config.week&&$D._validate(config.week,0,53,"week")){this.setWeek(config.week);}
37
+ return this;};$P.moveToFirstDayOfMonth=function(){return this.set({day:1});};$P.moveToLastDayOfMonth=function(){return this.set({day:$D.getDaysInMonth(this.getFullYear(),this.getMonth())});};$P.moveToNthOccurrence=function(dayOfWeek,occurrence){var shift=0;if(occurrence>0){shift=occurrence-1;}
38
+ else if(occurrence===-1){this.moveToLastDayOfMonth();if(this.getDay()!==dayOfWeek){this.moveToDayOfWeek(dayOfWeek,-1);}
39
+ return this;}
40
+ return this.moveToFirstDayOfMonth().addDays(-1).moveToDayOfWeek(dayOfWeek,+1).addWeeks(shift);};$P.moveToDayOfWeek=function(dayOfWeek,orient){var diff=(dayOfWeek-this.getDay()+7*(orient||+1))%7;return this.addDays((diff===0)?diff+=7*(orient||+1):diff);};$P.moveToMonth=function(month,orient){var diff=(month-this.getMonth()+12*(orient||+1))%12;return this.addMonths((diff===0)?diff+=12*(orient||+1):diff);};$P.getOrdinalNumber=function(){return Math.ceil((this.clone().clearTime()-new Date(this.getFullYear(),0,1))/86400000)+1;};$P.getTimezone=function(){return $D.getTimezoneAbbreviation(this.getUTCOffset());};$P.setTimezoneOffset=function(offset){var here=this.getTimezoneOffset(),there=Number(offset)*-6/10;return this.addMinutes(there-here);};$P.setTimezone=function(offset){return this.setTimezoneOffset($D.getTimezoneOffset(offset));};$P.hasDaylightSavingTime=function(){return(Date.today().set({month:0,day:1}).getTimezoneOffset()!==Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.isDaylightSavingTime=function(){return(this.hasDaylightSavingTime()&&new Date().getTimezoneOffset()===Date.today().set({month:6,day:1}).getTimezoneOffset());};$P.getUTCOffset=function(){var n=this.getTimezoneOffset()*-10/6,r;if(n<0){r=(n-10000).toString();return r.charAt(0)+r.substr(2);}else{r=(n+10000).toString();return"+"+r.substr(1);}};$P.getElapsed=function(date){return(date||new Date())-this;};if(!$P.toISOString){$P.toISOString=function(){function f(n){return n<10?'0'+n:n;}
41
+ return'"'+this.getUTCFullYear()+'-'+
42
+ f(this.getUTCMonth()+1)+'-'+
43
+ f(this.getUTCDate())+'T'+
44
+ f(this.getUTCHours())+':'+
45
+ f(this.getUTCMinutes())+':'+
46
+ f(this.getUTCSeconds())+'Z"';};}
47
+ $P._toString=$P.toString;$P.toString=function(format){var x=this;if(format&&format.length==1){var c=$C.formatPatterns;x.t=x.toString;switch(format){case"d":return x.t(c.shortDate);case"D":return x.t(c.longDate);case"F":return x.t(c.fullDateTime);case"m":return x.t(c.monthDay);case"r":return x.t(c.rfc1123);case"s":return x.t(c.sortableDateTime);case"t":return x.t(c.shortTime);case"T":return x.t(c.longTime);case"u":return x.t(c.universalSortableDateTime);case"y":return x.t(c.yearMonth);}}
48
+ var ord=function(n){switch(n*1){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th";}};return format?format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,function(m){if(m.charAt(0)==="\\"){return m.replace("\\","");}
49
+ x.h=x.getHours;switch(m){case"hh":return p(x.h()<13?(x.h()===0?12:x.h()):(x.h()-12));case"h":return x.h()<13?(x.h()===0?12:x.h()):(x.h()-12);case"HH":return p(x.h());case"H":return x.h();case"mm":return p(x.getMinutes());case"m":return x.getMinutes();case"ss":return p(x.getSeconds());case"s":return x.getSeconds();case"yyyy":return p(x.getFullYear(),4);case"yy":return p(x.getFullYear());case"dddd":return $C.dayNames[x.getDay()];case"ddd":return $C.abbreviatedDayNames[x.getDay()];case"dd":return p(x.getDate());case"d":return x.getDate();case"MMMM":return $C.monthNames[x.getMonth()];case"MMM":return $C.abbreviatedMonthNames[x.getMonth()];case"MM":return p((x.getMonth()+1));case"M":return x.getMonth()+1;case"t":return x.h()<12?$C.amDesignator.substring(0,1):$C.pmDesignator.substring(0,1);case"tt":return x.h()<12?$C.amDesignator:$C.pmDesignator;case"S":return ord(x.getDate());default:return m;}}):this._toString();};}());
50
+ (function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo,$N=Number.prototype;$P._orient=+1;$P._nth=null;$P._is=false;$P._same=false;$P._isSecond=false;$N._dateElement="day";$P.next=function(){this._orient=+1;return this;};$D.next=function(){return $D.today().next();};$P.last=$P.prev=$P.previous=function(){this._orient=-1;return this;};$D.last=$D.prev=$D.previous=function(){return $D.today().last();};$P.is=function(){this._is=true;return this;};$P.same=function(){this._same=true;this._isSecond=false;return this;};$P.today=function(){return this.same().day();};$P.weekday=function(){if(this._is){this._is=false;return(!this.is().sat()&&!this.is().sun());}
51
+ return false;};$P.at=function(time){return(typeof time==="string")?$D.parse(this.toString("d")+" "+time):this.set(time);};$N.fromNow=$N.after=function(date){var c={};c[this._dateElement]=this;return((!date)?new Date():date.clone()).add(c);};$N.ago=$N.before=function(date){var c={};c[this._dateElement]=this*-1;return((!date)?new Date():date.clone()).add(c);};var dx=("sunday monday tuesday wednesday thursday friday saturday").split(/\s/),mx=("january february march april may june july august september october november december").split(/\s/),px=("Millisecond Second Minute Hour Day Week Month Year").split(/\s/),pxf=("Milliseconds Seconds Minutes Hours Date Week Month FullYear").split(/\s/),nth=("final first second third fourth fifth").split(/\s/),de;$P.toObject=function(){var o={};for(var i=0;i<px.length;i++){o[px[i].toLowerCase()]=this["get"+pxf[i]]();}
52
+ return o;};$D.fromObject=function(config){config.week=null;return Date.today().set(config);};var df=function(n){return function(){if(this._is){this._is=false;return this.getDay()==n;}
53
+ if(this._nth!==null){if(this._isSecond){this.addSeconds(this._orient*-1);}
54
+ this._isSecond=false;var ntemp=this._nth;this._nth=null;var temp=this.clone().moveToLastDayOfMonth();this.moveToNthOccurrence(n,ntemp);if(this>temp){throw new RangeError($D.getDayName(n)+" does not occur "+ntemp+" times in the month of "+$D.getMonthName(temp.getMonth())+" "+temp.getFullYear()+".");}
55
+ return this;}
56
+ return this.moveToDayOfWeek(n,this._orient);};};var sdf=function(n){return function(){var t=$D.today(),shift=n-t.getDay();if(n===0&&$C.firstDayOfWeek===1&&t.getDay()!==0){shift=shift+7;}
57
+ return t.addDays(shift);};};for(var i=0;i<dx.length;i++){$D[dx[i].toUpperCase()]=$D[dx[i].toUpperCase().substring(0,3)]=i;$D[dx[i]]=$D[dx[i].substring(0,3)]=sdf(i);$P[dx[i]]=$P[dx[i].substring(0,3)]=df(i);}
58
+ var mf=function(n){return function(){if(this._is){this._is=false;return this.getMonth()===n;}
59
+ return this.moveToMonth(n,this._orient);};};var smf=function(n){return function(){return $D.today().set({month:n,day:1});};};for(var j=0;j<mx.length;j++){$D[mx[j].toUpperCase()]=$D[mx[j].toUpperCase().substring(0,3)]=j;$D[mx[j]]=$D[mx[j].substring(0,3)]=smf(j);$P[mx[j]]=$P[mx[j].substring(0,3)]=mf(j);}
60
+ var ef=function(j){return function(){if(this._isSecond){this._isSecond=false;return this;}
61
+ if(this._same){this._same=this._is=false;var o1=this.toObject(),o2=(arguments[0]||new Date()).toObject(),v="",k=j.toLowerCase();for(var m=(px.length-1);m>-1;m--){v=px[m].toLowerCase();if(o1[v]!=o2[v]){return false;}
62
+ if(k==v){break;}}
63
+ return true;}
64
+ if(j.substring(j.length-1)!="s"){j+="s";}
65
+ return this["add"+j](this._orient);};};var nf=function(n){return function(){this._dateElement=n;return this;};};for(var k=0;k<px.length;k++){de=px[k].toLowerCase();$P[de]=$P[de+"s"]=ef(px[k]);$N[de]=$N[de+"s"]=nf(de);}
66
+ $P._ss=ef("Second");var nthfn=function(n){return function(dayOfWeek){if(this._same){return this._ss(arguments[0]);}
67
+ if(dayOfWeek||dayOfWeek===0){return this.moveToNthOccurrence(dayOfWeek,n);}
68
+ this._nth=n;if(n===2&&(dayOfWeek===undefined||dayOfWeek===null)){this._isSecond=true;return this.addSeconds(this._orient);}
69
+ return this;};};for(var l=0;l<nth.length;l++){$P[nth[l]]=(l===0)?nthfn(-1):nthfn(l);}}());
70
+ (function(){Date.Parsing={Exception:function(s){this.message="Parse error at '"+s.substring(0,10)+" ...'";}};var $P=Date.Parsing;var _=$P.Operators={rtoken:function(r){return function(s){var mx=s.match(r);if(mx){return([mx[0],s.substring(mx[0].length)]);}else{throw new $P.Exception(s);}};},token:function(s){return function(s){return _.rtoken(new RegExp("^\s*"+s+"\s*"))(s);};},stoken:function(s){return _.rtoken(new RegExp("^"+s));},until:function(p){return function(s){var qx=[],rx=null;while(s.length){try{rx=p.call(this,s);}catch(e){qx.push(rx[0]);s=rx[1];continue;}
71
+ break;}
72
+ return[qx,s];};},many:function(p){return function(s){var rx=[],r=null;while(s.length){try{r=p.call(this,s);}catch(e){return[rx,s];}
73
+ rx.push(r[0]);s=r[1];}
74
+ return[rx,s];};},optional:function(p){return function(s){var r=null;try{r=p.call(this,s);}catch(e){return[null,s];}
75
+ return[r[0],r[1]];};},not:function(p){return function(s){try{p.call(this,s);}catch(e){return[null,s];}
76
+ throw new $P.Exception(s);};},ignore:function(p){return p?function(s){var r=null;r=p.call(this,s);return[null,r[1]];}:null;},product:function(){var px=arguments[0],qx=Array.prototype.slice.call(arguments,1),rx=[];for(var i=0;i<px.length;i++){rx.push(_.each(px[i],qx));}
77
+ return rx;},cache:function(rule){var cache={},r=null;return function(s){try{r=cache[s]=(cache[s]||rule.call(this,s));}catch(e){r=cache[s]=e;}
78
+ if(r instanceof $P.Exception){throw r;}else{return r;}};},any:function(){var px=arguments;return function(s){var r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
79
+ try{r=(px[i].call(this,s));}catch(e){r=null;}
80
+ if(r){return r;}}
81
+ throw new $P.Exception(s);};},each:function(){var px=arguments;return function(s){var rx=[],r=null;for(var i=0;i<px.length;i++){if(px[i]==null){continue;}
82
+ try{r=(px[i].call(this,s));}catch(e){throw new $P.Exception(s);}
83
+ rx.push(r[0]);s=r[1];}
84
+ return[rx,s];};},all:function(){var px=arguments,_=_;return _.each(_.optional(px));},sequence:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;if(px.length==1){return px[0];}
85
+ return function(s){var r=null,q=null;var rx=[];for(var i=0;i<px.length;i++){try{r=px[i].call(this,s);}catch(e){break;}
86
+ rx.push(r[0]);try{q=d.call(this,r[1]);}catch(ex){q=null;break;}
87
+ s=q[1];}
88
+ if(!r){throw new $P.Exception(s);}
89
+ if(q){throw new $P.Exception(q[1]);}
90
+ if(c){try{r=c.call(this,r[1]);}catch(ey){throw new $P.Exception(r[1]);}}
91
+ return[rx,(r?r[1]:s)];};},between:function(d1,p,d2){d2=d2||d1;var _fn=_.each(_.ignore(d1),p,_.ignore(d2));return function(s){var rx=_fn.call(this,s);return[[rx[0][0],r[0][2]],rx[1]];};},list:function(p,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return(p instanceof Array?_.each(_.product(p.slice(0,-1),_.ignore(d)),p.slice(-1),_.ignore(c)):_.each(_.many(_.each(p,_.ignore(d))),px,_.ignore(c)));},set:function(px,d,c){d=d||_.rtoken(/^\s*/);c=c||null;return function(s){var r=null,p=null,q=null,rx=null,best=[[],s],last=false;for(var i=0;i<px.length;i++){q=null;p=null;r=null;last=(px.length==1);try{r=px[i].call(this,s);}catch(e){continue;}
92
+ rx=[[r[0]],r[1]];if(r[1].length>0&&!last){try{q=d.call(this,r[1]);}catch(ex){last=true;}}else{last=true;}
93
+ if(!last&&q[1].length===0){last=true;}
94
+ if(!last){var qx=[];for(var j=0;j<px.length;j++){if(i!=j){qx.push(px[j]);}}
95
+ p=_.set(qx,d).call(this,q[1]);if(p[0].length>0){rx[0]=rx[0].concat(p[0]);rx[1]=p[1];}}
96
+ if(rx[1].length<best[1].length){best=rx;}
97
+ if(best[1].length===0){break;}}
98
+ if(best[0].length===0){return best;}
99
+ if(c){try{q=c.call(this,best[1]);}catch(ey){throw new $P.Exception(best[1]);}
100
+ best[1]=q[1];}
101
+ return best;};},forward:function(gr,fname){return function(s){return gr[fname].call(this,s);};},replace:function(rule,repl){return function(s){var r=rule.call(this,s);return[repl,r[1]];};},process:function(rule,fn){return function(s){var r=rule.call(this,s);return[fn.call(this,r[0]),r[1]];};},min:function(min,rule){return function(s){var rx=rule.call(this,s);if(rx[0].length<min){throw new $P.Exception(s);}
102
+ return rx;};}};var _generator=function(op){return function(){var args=null,rx=[];if(arguments.length>1){args=Array.prototype.slice.call(arguments);}else if(arguments[0]instanceof Array){args=arguments[0];}
103
+ if(args){for(var i=0,px=args.shift();i<px.length;i++){args.unshift(px[i]);rx.push(op.apply(null,args));args.shift();return rx;}}else{return op.apply(null,arguments);}};};var gx="optional not ignore cache".split(/\s/);for(var i=0;i<gx.length;i++){_[gx[i]]=_generator(_[gx[i]]);}
104
+ var _vector=function(op){return function(){if(arguments[0]instanceof Array){return op.apply(null,arguments[0]);}else{return op.apply(null,arguments);}};};var vx="each any all".split(/\s/);for(var j=0;j<vx.length;j++){_[vx[j]]=_vector(_[vx[j]]);}}());(function(){var $D=Date,$P=$D.prototype,$C=$D.CultureInfo;var flattenAndCompact=function(ax){var rx=[];for(var i=0;i<ax.length;i++){if(ax[i]instanceof Array){rx=rx.concat(flattenAndCompact(ax[i]));}else{if(ax[i]){rx.push(ax[i]);}}}
105
+ return rx;};$D.Grammar={};$D.Translator={hour:function(s){return function(){this.hour=Number(s);};},minute:function(s){return function(){this.minute=Number(s);};},second:function(s){return function(){this.second=Number(s);};},meridian:function(s){return function(){this.meridian=s.slice(0,1).toLowerCase();};},timezone:function(s){return function(){var n=s.replace(/[^\d\+\-]/g,"");if(n.length){this.timezoneOffset=Number(n);}else{this.timezone=s.toLowerCase();}};},day:function(x){var s=x[0];return function(){this.day=Number(s.match(/\d+/)[0]);};},month:function(s){return function(){this.month=(s.length==3)?"jan feb mar apr may jun jul aug sep oct nov dec".indexOf(s)/4:Number(s)-1;};},year:function(s){return function(){var n=Number(s);this.year=((s.length>2)?n:(n+(((n+2000)<$C.twoDigitYearMax)?2000:1900)));};},rday:function(s){return function(){switch(s){case"yesterday":this.days=-1;break;case"tomorrow":this.days=1;break;case"today":this.days=0;break;case"now":this.days=0;this.now=true;break;}};},finishExact:function(x){x=(x instanceof Array)?x:[x];for(var i=0;i<x.length;i++){if(x[i]){x[i].call(this);}}
106
+ var now=new Date();if((this.hour||this.minute)&&(!this.month&&!this.year&&!this.day)){this.day=now.getDate();}
107
+ if(!this.year){this.year=now.getFullYear();}
108
+ if(!this.month&&this.month!==0){this.month=now.getMonth();}
109
+ if(!this.day){this.day=1;}
110
+ if(!this.hour){this.hour=0;}
111
+ if(!this.minute){this.minute=0;}
112
+ if(!this.second){this.second=0;}
113
+ if(this.meridian&&this.hour){if(this.meridian=="p"&&this.hour<12){this.hour=this.hour+12;}else if(this.meridian=="a"&&this.hour==12){this.hour=0;}}
114
+ if(this.day>$D.getDaysInMonth(this.year,this.month)){throw new RangeError(this.day+" is not a valid value for days.");}
115
+ var r=new Date(this.year,this.month,this.day,this.hour,this.minute,this.second);if(this.timezone){r.set({timezone:this.timezone});}else if(this.timezoneOffset){r.set({timezoneOffset:this.timezoneOffset});}
116
+ return r;},finish:function(x){x=(x instanceof Array)?flattenAndCompact(x):[x];if(x.length===0){return null;}
117
+ for(var i=0;i<x.length;i++){if(typeof x[i]=="function"){x[i].call(this);}}
118
+ var today=$D.today();if(this.now&&!this.unit&&!this.operator){return new Date();}else if(this.now){today=new Date();}
119
+ var expression=!!(this.days&&this.days!==null||this.orient||this.operator);var gap,mod,orient;orient=((this.orient=="past"||this.operator=="subtract")?-1:1);if(!this.now&&"hour minute second".indexOf(this.unit)!=-1){today.setTimeToNow();}
120
+ if(this.month||this.month===0){if("year day hour minute second".indexOf(this.unit)!=-1){this.value=this.month+1;this.month=null;expression=true;}}
121
+ if(!expression&&this.weekday&&!this.day&&!this.days){var temp=Date[this.weekday]();this.day=temp.getDate();if(!this.month){this.month=temp.getMonth();}
122
+ this.year=temp.getFullYear();}
123
+ if(expression&&this.weekday&&this.unit!="month"){this.unit="day";gap=($D.getDayNumberFromName(this.weekday)-today.getDay());mod=7;this.days=gap?((gap+(orient*mod))%mod):(orient*mod);}
124
+ if(this.month&&this.unit=="day"&&this.operator){this.value=(this.month+1);this.month=null;}
125
+ if(this.value!=null&&this.month!=null&&this.year!=null){this.day=this.value*1;}
126
+ if(this.month&&!this.day&&this.value){today.set({day:this.value*1});if(!expression){this.day=this.value*1;}}
127
+ if(!this.month&&this.value&&this.unit=="month"&&!this.now){this.month=this.value;expression=true;}
128
+ if(expression&&(this.month||this.month===0)&&this.unit!="year"){this.unit="month";gap=(this.month-today.getMonth());mod=12;this.months=gap?((gap+(orient*mod))%mod):(orient*mod);this.month=null;}
129
+ if(!this.unit){this.unit="day";}
130
+ if(!this.value&&this.operator&&this.operator!==null&&this[this.unit+"s"]&&this[this.unit+"s"]!==null){this[this.unit+"s"]=this[this.unit+"s"]+((this.operator=="add")?1:-1)+(this.value||0)*orient;}else if(this[this.unit+"s"]==null||this.operator!=null){if(!this.value){this.value=1;}
131
+ this[this.unit+"s"]=this.value*orient;}
132
+ if(this.meridian&&this.hour){if(this.meridian=="p"&&this.hour<12){this.hour=this.hour+12;}else if(this.meridian=="a"&&this.hour==12){this.hour=0;}}
133
+ if(this.weekday&&!this.day&&!this.days){var temp=Date[this.weekday]();this.day=temp.getDate();if(temp.getMonth()!==today.getMonth()){this.month=temp.getMonth();}}
134
+ if((this.month||this.month===0)&&!this.day){this.day=1;}
135
+ if(!this.orient&&!this.operator&&this.unit=="week"&&this.value&&!this.day&&!this.month){return Date.today().setWeek(this.value);}
136
+ if(expression&&this.timezone&&this.day&&this.days){this.day=this.days;}
137
+ return(expression)?today.add(this):today.set(this);}};var _=$D.Parsing.Operators,g=$D.Grammar,t=$D.Translator,_fn;g.datePartDelimiter=_.rtoken(/^([\s\-\.\,\/\x27]+)/);g.timePartDelimiter=_.stoken(":");g.whiteSpace=_.rtoken(/^\s*/);g.generalDelimiter=_.rtoken(/^(([\s\,]|at|@|on)+)/);var _C={};g.ctoken=function(keys){var fn=_C[keys];if(!fn){var c=$C.regexPatterns;var kx=keys.split(/\s+/),px=[];for(var i=0;i<kx.length;i++){px.push(_.replace(_.rtoken(c[kx[i]]),kx[i]));}
138
+ fn=_C[keys]=_.any.apply(null,px);}
139
+ return fn;};g.ctoken2=function(key){return _.rtoken($C.regexPatterns[key]);};g.h=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2]|[1-9])/),t.hour));g.hh=_.cache(_.process(_.rtoken(/^(0[0-9]|1[0-2])/),t.hour));g.H=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3]|[0-9])/),t.hour));g.HH=_.cache(_.process(_.rtoken(/^([0-1][0-9]|2[0-3])/),t.hour));g.m=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.minute));g.mm=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.minute));g.s=_.cache(_.process(_.rtoken(/^([0-5][0-9]|[0-9])/),t.second));g.ss=_.cache(_.process(_.rtoken(/^[0-5][0-9]/),t.second));g.hms=_.cache(_.sequence([g.H,g.m,g.s],g.timePartDelimiter));g.t=_.cache(_.process(g.ctoken2("shortMeridian"),t.meridian));g.tt=_.cache(_.process(g.ctoken2("longMeridian"),t.meridian));g.z=_.cache(_.process(_.rtoken(/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/),t.timezone));g.zz=_.cache(_.process(_.rtoken(/^((\+|\-)\s*\d\d\d\d)|((\+|\-)\d\d\:?\d\d)/),t.timezone));g.zzz=_.cache(_.process(g.ctoken2("timezone"),t.timezone));g.timeSuffix=_.each(_.ignore(g.whiteSpace),_.set([g.tt,g.zzz]));g.time=_.each(_.optional(_.ignore(_.stoken("T"))),g.hms,g.timeSuffix);g.d=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1]|\d)/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.dd=_.cache(_.process(_.each(_.rtoken(/^([0-2]\d|3[0-1])/),_.optional(g.ctoken2("ordinalSuffix"))),t.day));g.ddd=g.dddd=_.cache(_.process(g.ctoken("sun mon tue wed thu fri sat"),function(s){return function(){this.weekday=s;};}));g.M=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d|\d)/),t.month));g.MM=_.cache(_.process(_.rtoken(/^(1[0-2]|0\d)/),t.month));g.MMM=g.MMMM=_.cache(_.process(g.ctoken("jan feb mar apr may jun jul aug sep oct nov dec"),t.month));g.y=_.cache(_.process(_.rtoken(/^(\d\d?)/),t.year));g.yy=_.cache(_.process(_.rtoken(/^(\d\d)/),t.year));g.yyy=_.cache(_.process(_.rtoken(/^(\d\d?\d?\d?)/),t.year));g.yyyy=_.cache(_.process(_.rtoken(/^(\d\d\d\d)/),t.year));_fn=function(){return _.each(_.any.apply(null,arguments),_.not(g.ctoken2("timeContext")));};g.day=_fn(g.d,g.dd);g.month=_fn(g.M,g.MMM);g.year=_fn(g.yyyy,g.yy);g.orientation=_.process(g.ctoken("past future"),function(s){return function(){this.orient=s;};});g.operator=_.process(g.ctoken("add subtract"),function(s){return function(){this.operator=s;};});g.rday=_.process(g.ctoken("yesterday tomorrow today now"),t.rday);g.unit=_.process(g.ctoken("second minute hour day week month year"),function(s){return function(){this.unit=s;};});g.value=_.process(_.rtoken(/^\d\d?(st|nd|rd|th)?/),function(s){return function(){this.value=s.replace(/\D/g,"");};});g.expression=_.set([g.rday,g.operator,g.value,g.unit,g.orientation,g.ddd,g.MMM]);_fn=function(){return _.set(arguments,g.datePartDelimiter);};g.mdy=_fn(g.ddd,g.month,g.day,g.year);g.ymd=_fn(g.ddd,g.year,g.month,g.day);g.dmy=_fn(g.ddd,g.day,g.month,g.year);g.date=function(s){return((g[$C.dateElementOrder]||g.mdy).call(this,s));};g.format=_.process(_.many(_.any(_.process(_.rtoken(/^(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|zz?z?)/),function(fmt){if(g[fmt]){return g[fmt];}else{throw $D.Parsing.Exception(fmt);}}),_.process(_.rtoken(/^[^dMyhHmstz]+/),function(s){return _.ignore(_.stoken(s));}))),function(rules){return _.process(_.each.apply(null,rules),t.finishExact);});var _F={};var _get=function(f){return _F[f]=(_F[f]||g.format(f)[0]);};g.formats=function(fx){if(fx instanceof Array){var rx=[];for(var i=0;i<fx.length;i++){rx.push(_get(fx[i]));}
140
+ return _.any.apply(null,rx);}else{return _get(fx);}};g._formats=g.formats(["\"yyyy-MM-ddTHH:mm:ssZ\"","yyyy-MM-ddTHH:mm:ssZ","yyyy-MM-ddTHH:mm:ssz","yyyy-MM-ddTHH:mm:ss","yyyy-MM-ddTHH:mmZ","yyyy-MM-ddTHH:mmz","yyyy-MM-ddTHH:mm","ddd, MMM dd, yyyy H:mm:ss tt","ddd MMM d yyyy HH:mm:ss zzz","MMddyyyy","ddMMyyyy","Mddyyyy","ddMyyyy","Mdyyyy","dMyyyy","yyyy","Mdyy","dMyy","d"]);g._start=_.process(_.set([g.date,g.time,g.expression],g.generalDelimiter,g.whiteSpace),t.finish);g.start=function(s){try{var r=g._formats.call({},s);if(r[1].length===0){return r;}}catch(e){}
141
+ return g._start.call({},s);};$D._parse=$D.parse;$D.parse=function(s){var r=null;if(!s){return null;}
142
+ if(s instanceof Date){return s;}
143
+ try{r=$D.Grammar.start.call({},s.replace(/^\s*(\S*(\s+\S+)*)\s*$/,"$1"));}catch(e){return null;}
144
+ return((r[1].length===0)?r[0]:null);};$D.getParseFunction=function(fx){var fn=$D.Grammar.formats(fx);return function(s){var r=null;try{r=fn.call({},s);}catch(e){return null;}
145
+ return((r[1].length===0)?r[0]:null);};};$D.parseExact=function(s,fx){return $D.getParseFunction(fx)(s);};}());
146
+
147
+
148
+ /*
149
+ * jQuery.weekCalendar v2.0-dev
150
+ *
151
+ * for support join us at the google group:
152
+ * - http://groups.google.com/group/jquery-week-calendar
153
+ * have a look to the wiki for documentation:
154
+ * - http://wiki.github.com/themouette/jquery-week-calendar/
155
+ * something went bad ? report an issue:
156
+ * - http://github.com/themouette/jquery-week-calendar/issues
157
+ * get the last version on github:
158
+ * - http://github.com/themouette/jquery-week-calendar
159
+ *
160
+ * Copyright (c) 2009 Rob Monie
161
+ * Copyright (c) 2010 Julien MUETTON
162
+ * Dual licensed under the MIT and GPL licenses:
163
+ * http://www.opensource.org/licenses/mit-license.php
164
+ * http://www.gnu.org/licenses/gpl.html
165
+ *
166
+ * If you're after a monthly calendar plugin, check out this one :
167
+ * http://arshaw.com/fullcalendar/
168
+ */
169
+
170
+ (function($) {
171
+ // check the jquery version
172
+ var _v = $.fn.jquery.split('.'),
173
+ _jQuery14OrLower = (10 * _v[0] + _v[1]) < 15;
174
+
175
+ $.widget('ui.weekCalendar', (function() {
176
+ var _currentAjaxCall;
177
+ return {
178
+ options: {
179
+ date: new Date(),
180
+ timeFormat: null,
181
+ dateFormat: 'M d, Y',
182
+ alwaysDisplayTimeMinutes: true,
183
+ use24Hour: false,
184
+ daysToShow: 7,
185
+ minBodyHeight: 100,
186
+ firstDayOfWeek: function(calendar) {
187
+ if ($(calendar).weekCalendar('option', 'daysToShow') != 5) {
188
+ return 0;
189
+ } else {
190
+ //workweek
191
+ return 1;
192
+ }
193
+ }, // 0 = Sunday, 1 = Monday, 2 = Tuesday, ... , 6 = Saturday
194
+ useShortDayNames: false,
195
+ timeSeparator: ' to ',
196
+ startParam: 'start',
197
+ endParam: 'end',
198
+ businessHours: {start: 8, end: 18, limitDisplay: false},
199
+ newEventText: 'New Event',
200
+ timeslotHeight: 20,
201
+ defaultEventLength: 2,
202
+ timeslotsPerHour: 4,
203
+ minDate: null,
204
+ maxDate: null,
205
+ buttons: true,
206
+ buttonText: {
207
+ today: 'today',
208
+ lastWeek: 'previous',
209
+ nextWeek: 'next'
210
+ },
211
+ switchDisplay: {},
212
+ scrollToHourMillis: 500,
213
+ allowEventDelete: false,
214
+ allowCalEventOverlap: false,
215
+ overlapEventsSeparate: false,
216
+ totalEventsWidthPercentInOneColumn : 100,
217
+ readonly: false,
218
+ allowEventCreation: true,
219
+ deletable: function(calEvent, element) {
220
+ return true;
221
+ },
222
+ draggable: function(calEvent, element) {
223
+ return true;
224
+ },
225
+ resizable: function(calEvent, element) {
226
+ return true;
227
+ },
228
+ eventClick: function(calEvent, element, dayFreeBusyManager,
229
+ calendar, clickEvent) {
230
+ },
231
+ eventRender: function(calEvent, element) {
232
+ return element;
233
+ },
234
+ eventAfterRender: function(calEvent, element) {
235
+ return element;
236
+ },
237
+ eventRefresh: function(calEvent, element) {
238
+ return element;
239
+ },
240
+ eventDrag: function(calEvent, element) {
241
+ },
242
+ eventDrop: function(calEvent, element) {
243
+ },
244
+ eventResize: function(calEvent, element) {
245
+ },
246
+ eventNew: function(calEvent, element, dayFreeBusyManager,
247
+ calendar, mouseupEvent) {
248
+ },
249
+ eventMouseover: function(calEvent, $event) {
250
+ },
251
+ eventMouseout: function(calEvent, $event) {
252
+ },
253
+ eventDelete: function(calEvent, element, dayFreeBusyManager,
254
+ calendar, clickEvent) {
255
+ calendar.weekCalendar('removeEvent',calEvent.id);
256
+ },
257
+ calendarBeforeLoad: function(calendar) {
258
+ },
259
+ calendarAfterLoad: function(calendar) {
260
+ },
261
+ noEvents: function() {
262
+ },
263
+ eventHeader: function(calEvent, calendar) {
264
+ var options = calendar.weekCalendar('option');
265
+ var one_hour = 3600000;
266
+ var displayTitleWithTime = calEvent.end.getTime() - calEvent.start.getTime() <= (one_hour / options.timeslotsPerHour);
267
+ if (displayTitleWithTime) {
268
+ return calendar.weekCalendar(
269
+ 'formatTime', calEvent.start) +
270
+ ': ' + calEvent.title;
271
+ } else {
272
+ return calendar.weekCalendar(
273
+ 'formatTime', calEvent.start) +
274
+ options.timeSeparator +
275
+ calendar.weekCalendar(
276
+ 'formatTime', calEvent.end);
277
+ }
278
+ },
279
+ eventBody: function(calEvent, calendar) {
280
+ return calEvent.title;
281
+ },
282
+ shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
283
+ longMonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
284
+ shortDays: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
285
+ longDays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
286
+ /* multi-users options */
287
+ /**
288
+ * the available users for calendar.
289
+ * if you want to display users separately, enable the
290
+ * showAsSeparateUsers option.
291
+ * if you provide a list of user and do not enable showAsSeparateUsers
292
+ * option, then only the events that belongs to one or several of
293
+ * given users will be displayed
294
+ * @type {array}
295
+ */
296
+ users: [],
297
+ /**
298
+ * should the calendar be displayed with separate column for each
299
+ * users.
300
+ * note that this option does nothing if you do not provide at least
301
+ * one user.
302
+ * @type {boolean}
303
+ */
304
+ showAsSeparateUsers: true,
305
+ /**
306
+ * callback used to read user id from a user object.
307
+ * @param {Object} user the user to retrieve the id from.
308
+ * @param {number} index the user index from user list.
309
+ * @param {jQuery} calendar the calendar object.
310
+ * @return {int|String} the user id.
311
+ */
312
+ getUserId: function(user, index, calendar) {
313
+ return index;
314
+ },
315
+ /**
316
+ * callback used to read user name from a user object.
317
+ * @param {Object} user the user to retrieve the name from.
318
+ * @param {number} index the user index from user list.
319
+ * @param {jQuery} calendar the calendar object.
320
+ * @return {String} the user name.
321
+ */
322
+ getUserName: function(user, index, calendar) {
323
+ return user;
324
+ },
325
+ /**
326
+ * reads the id(s) of user(s) for who the event should be displayed.
327
+ * @param {Object} calEvent the calEvent to read informations from.
328
+ * @param {jQuery} calendar the calendar object.
329
+ * @return {number|String|Array} the user id(s) to appened events for.
330
+ */
331
+ getEventUserId: function(calEvent, calendar) {
332
+ return calEvent.userId;
333
+ },
334
+ /**
335
+ * sets user id(s) to the calEvent
336
+ * @param {Object} calEvent the calEvent to set informations to.
337
+ * @param {jQuery} calendar the calendar object.
338
+ * @return {Object} the calEvent with modified user id.
339
+ */
340
+ setEventUserId: function(userId, calEvent, calendar) {
341
+ calEvent.userId = userId;
342
+ return calEvent;
343
+ },
344
+ /* freeBusy options */
345
+ /**
346
+ * should the calendar display freebusys ?
347
+ * @type {boolean}
348
+ */
349
+ displayFreeBusys: false,
350
+ /**
351
+ * read the id(s) for who the freebusy is available
352
+ * @param {Object} calEvent the calEvent to read informations from.
353
+ * @param {jQuery} calendar the calendar object.
354
+ * @return {number|String|Array} the user id(s) to appened events for.
355
+ */
356
+ getFreeBusyUserId: function(calFreeBusy, calendar) {
357
+ return calFreeBusy.userId;
358
+ },
359
+ /**
360
+ * the default freeBusy object, used to manage default state
361
+ * @type {Object}
362
+ */
363
+ defaultFreeBusy: {free: false},
364
+ /**
365
+ * function used to display the freeBusy element
366
+ * @type {Function}
367
+ * @param {Object} freeBusy the freeBusy timeslot to render.
368
+ * @param {jQuery} $freeBusy the freeBusy HTML element.
369
+ * @param {jQuery} calendar the calendar element.
370
+ */
371
+ freeBusyRender: function(freeBusy, $freeBusy, calendar) {
372
+ if (!freeBusy.free) {
373
+ $freeBusy.addClass('free-busy-busy');
374
+ }
375
+ else {
376
+ $freeBusy.addClass('free-busy-free');
377
+ }
378
+ return $freeBusy;
379
+ },
380
+ /* other options */
381
+ /**
382
+ * true means start on first day of week, false means starts on
383
+ * startDate.
384
+ * @param {jQuery} calendar the calendar object.
385
+ * @type {Function|bool}
386
+ */
387
+ startOnFirstDayOfWeek: function(calendar) {
388
+ return $(calendar).weekCalendar('option', 'daysToShow') >= 5;
389
+ },
390
+ /**
391
+ * should the columns be rendered alternatively using odd/even
392
+ * class
393
+ * @type {boolean}
394
+ */
395
+ displayOddEven: false,
396
+ textSize: 13,
397
+ /**
398
+ * the title attribute for the calendar. possible placeholders are:
399
+ * <ul>
400
+ * <li>%start%</li>
401
+ * <li>%end%</li>
402
+ * <li>%date%</li>
403
+ * </ul>
404
+ * @type {Function|string}
405
+ * @param {number} option daysToShow.
406
+ * @return {String} the title attribute for the calendar.
407
+ */
408
+ title: '%start% - %end%',
409
+ /**
410
+ * default options to pass to callback
411
+ * you can pass a function returning an object or a litteral object
412
+ * @type {object|function(#calendar)}
413
+ */
414
+ jsonOptions: {},
415
+ headerSeparator: '<br />',
416
+ /**
417
+ * returns formatted header for day display
418
+ * @type {function(date,calendar)}
419
+ */
420
+ getHeaderDate: null,
421
+ preventDragOnEventCreation: false,
422
+ /**
423
+ * the event on which to bind calendar resize
424
+ * @type {string}
425
+ */
426
+ resizeEvent: 'resize.weekcalendar'
427
+ },
428
+
429
+ /***********************
430
+ * Initialise calendar *
431
+ ***********************/
432
+ _create: function() {
433
+ var self = this;
434
+ self._computeOptions();
435
+ self._setupEventDelegation();
436
+ self._renderCalendar();
437
+ self._loadCalEvents();
438
+ self._resizeCalendar();
439
+ self._scrollToHour(self.options.date.getHours(), true);
440
+
441
+ if (this.options.resizeEvent) {
442
+ $(window).unbind(this.options.resizeEvent);
443
+ $(window).bind(this.options.resizeEvent, function() {
444
+ self._resizeCalendar();
445
+ });
446
+ }
447
+
448
+ },
449
+
450
+ /********************
451
+ * public functions *
452
+ ********************/
453
+ /*
454
+ * Refresh the events for the currently displayed week.
455
+ */
456
+ refresh: function() {
457
+ //reload with existing week
458
+ this._loadCalEvents(this.element.data('startDate'));
459
+ },
460
+
461
+ /*
462
+ * Clear all events currently loaded into the calendar
463
+ */
464
+ clear: function() {
465
+ this._clearCalendar();
466
+ },
467
+
468
+ /*
469
+ * Go to this week
470
+ */
471
+ today: function() {
472
+ this._clearCalendar();
473
+ this._loadCalEvents(new Date());
474
+ },
475
+
476
+ /*
477
+ * Go to the previous week relative to the currently displayed week
478
+ */
479
+ prevWeek: function() {
480
+ //minus more than 1 day to be sure we're in previous week - account for daylight savings or other anomolies
481
+ var newDate = new Date(this.element.data('startDate').getTime() - (MILLIS_IN_WEEK / 6));
482
+ this._clearCalendar();
483
+ this._loadCalEvents(newDate);
484
+ },
485
+
486
+ /*
487
+ * Go to the next week relative to the currently displayed week
488
+ */
489
+ nextWeek: function() {
490
+ //add 8 days to be sure of being in prev week - allows for daylight savings or other anomolies
491
+ var newDate = new Date(this.element.data('startDate').getTime() + MILLIS_IN_WEEK + MILLIS_IN_DAY);
492
+ this._clearCalendar();
493
+ this._loadCalEvents(newDate);
494
+ },
495
+
496
+ /*
497
+ * Reload the calendar to whatever week the date passed in falls on.
498
+ */
499
+ gotoWeek: function(date) {
500
+ this._clearCalendar();
501
+ this._loadCalEvents(date);
502
+ },
503
+
504
+ /*
505
+ * Reload the calendar to whatever week the date passed in falls on.
506
+ */
507
+ gotoDate: function(date) {
508
+ this._clearCalendar();
509
+ this._loadCalEvents(date);
510
+ },
511
+
512
+ /**
513
+ * change the number of days to show
514
+ */
515
+ setDaysToShow: function(daysToShow) {
516
+ var self = this;
517
+ var hour = self._getCurrentScrollHour();
518
+ self.options.daysToShow = daysToShow;
519
+ $(self.element).html('');
520
+ self._renderCalendar();
521
+ self._loadCalEvents();
522
+ self._resizeCalendar();
523
+ self._scrollToHour(hour, false);
524
+
525
+ if (this.options.resizeEvent) {
526
+ $(window).unbind(this.options.resizeEvent);
527
+ $(window).bind(this.options.resizeEvent, function() {
528
+ self._resizeCalendar();
529
+ });
530
+ }
531
+ },
532
+
533
+ /*
534
+ * Remove an event based on it's id
535
+ */
536
+ removeEvent: function(eventId) {
537
+
538
+ var self = this;
539
+
540
+ self.element.find('.wc-cal-event').each(function() {
541
+ if ($(this).data('calEvent').id === eventId) {
542
+ $(this).remove();
543
+ return false;
544
+ }
545
+ });
546
+
547
+ //this could be more efficient rather than running on all days regardless...
548
+ self.element.find('.wc-day-column-inner').each(function() {
549
+ self._adjustOverlappingEvents($(this));
550
+ });
551
+ },
552
+
553
+ /*
554
+ * Removes any events that have been added but not yet saved (have no id).
555
+ * This is useful to call after adding a freshly saved new event.
556
+ */
557
+ removeUnsavedEvents: function() {
558
+
559
+ var self = this;
560
+
561
+ self.element.find('.wc-new-cal-event').each(function() {
562
+ $(this).remove();
563
+ });
564
+
565
+ //this could be more efficient rather than running on all days regardless...
566
+ self.element.find('.wc-day-column-inner').each(function() {
567
+ self._adjustOverlappingEvents($(this));
568
+ });
569
+ },
570
+
571
+ /*
572
+ * update an event in the calendar. If the event exists it refreshes
573
+ * it's rendering. If it's a new event that does not exist in the calendar
574
+ * it will be added.
575
+ */
576
+ updateEvent: function(calEvent) {
577
+ this._updateEventInCalendar(calEvent);
578
+ },
579
+
580
+ /*
581
+ * Returns an array of timeslot start and end times based on
582
+ * the configured grid of the calendar. Returns in both date and
583
+ * formatted time based on the 'timeFormat' config option.
584
+ */
585
+ getTimeslotTimes: function(date) {
586
+ var options = this.options;
587
+ var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
588
+ var startDate = new Date(date.getFullYear(), date.getMonth(), date.getDate(), firstHourDisplayed);
589
+
590
+ var times = [],
591
+ startMillis = startDate.getTime();
592
+ for (var i = 0; i < options.timeslotsPerDay; i++) {
593
+ var endMillis = startMillis + options.millisPerTimeslot;
594
+ times[i] = {
595
+ start: new Date(startMillis),
596
+ startFormatted: this.formatTime(new Date(startMillis), options.timeFormat),
597
+ end: new Date(endMillis),
598
+ endFormatted: this.formatTime(new Date(endMillis), options.timeFormat)
599
+ };
600
+ startMillis = endMillis;
601
+ }
602
+ return times;
603
+ },
604
+
605
+ formatDate: function(date, format) {
606
+ if (format) {
607
+ return this._formatDate(date, format);
608
+ } else {
609
+ return this._formatDate(date, this.options.dateFormat);
610
+ }
611
+ },
612
+
613
+ formatTime: function(date, format) {
614
+ if (format) {
615
+ return this._formatDate(date, format);
616
+ } else if (this.options.timeFormat) {
617
+ return this._formatDate(date, this.options.timeFormat);
618
+ } else if (this.options.use24Hour) {
619
+ return this._formatDate(date, 'H:i');
620
+ } else {
621
+ return this._formatDate(date, 'h:i a');
622
+ }
623
+ },
624
+
625
+ serializeEvents: function() {
626
+ var self = this;
627
+ var calEvents = [];
628
+
629
+ self.element.find('.wc-cal-event').each(function() {
630
+ calEvents.push($(this).data('calEvent'));
631
+ });
632
+ return calEvents;
633
+ },
634
+
635
+ next: function() {
636
+ if (this._startOnFirstDayOfWeek()) {
637
+ return this.nextWeek();
638
+ }
639
+ var newDate = new Date(this.element.data('startDate').getTime());
640
+ newDate.setDate(newDate.getDate() + this.options.daysToShow);
641
+
642
+ this._clearCalendar();
643
+ this._loadCalEvents(newDate);
644
+ },
645
+
646
+ prev: function() {
647
+ if (this._startOnFirstDayOfWeek()) {
648
+ return this.prevWeek();
649
+ }
650
+ var newDate = new Date(this.element.data('startDate').getTime());
651
+ newDate.setDate(newDate.getDate() - this.options.daysToShow);
652
+
653
+ this._clearCalendar();
654
+ this._loadCalEvents(newDate);
655
+ },
656
+ getCurrentFirstDay: function() {
657
+ return this._dateFirstDayOfWeek(this.options.date || new Date());
658
+ },
659
+ getCurrentLastDay: function() {
660
+ return this._addDays(this.getCurrentFirstDay(), this.options.daysToShow - 1);
661
+ },
662
+
663
+ /*********************
664
+ * private functions *
665
+ *********************/
666
+ _setOption: function(key, value) {
667
+ var self = this;
668
+ if (self.options[key] != value) {
669
+ // event callback change, no need to re-render the events
670
+ if (key == 'beforeEventNew') {
671
+ self.options[key] = value;
672
+ return;
673
+ }
674
+
675
+ // this could be made more efficient at some stage by caching the
676
+ // events array locally in a store but this should be done in conjunction
677
+ // with a proper binding model.
678
+
679
+ var currentEvents = $.map(self.element.find('.wc-cal-event'), function() {
680
+ return $(this).data('calEvent');
681
+ });
682
+
683
+ var newOptions = {};
684
+ newOptions[key] = value;
685
+ self._renderEvents({events: currentEvents, options: newOptions}, self.element.find('.wc-day-column-inner'));
686
+ }
687
+ },
688
+
689
+ // compute dynamic options based on other config values
690
+ _computeOptions: function() {
691
+ var options = this.options;
692
+ if (options.businessHours.limitDisplay) {
693
+ options.timeslotsPerDay = options.timeslotsPerHour * (options.businessHours.end - options.businessHours.start);
694
+ options.millisToDisplay = (options.businessHours.end - options.businessHours.start) * 3600000; // 60 * 60 * 1000
695
+ options.millisPerTimeslot = options.millisToDisplay / options.timeslotsPerDay;
696
+ } else {
697
+ options.timeslotsPerDay = options.timeslotsPerHour * 24;
698
+ options.millisToDisplay = MILLIS_IN_DAY;
699
+ options.millisPerTimeslot = MILLIS_IN_DAY / options.timeslotsPerDay;
700
+ }
701
+ },
702
+
703
+ /*
704
+ * Resize the calendar scrollable height based on the provided function in options.
705
+ */
706
+ _resizeCalendar: function() {
707
+ var options = this.options;
708
+ if (options && $.isFunction(options.height)) {
709
+ var calendarHeight = options.height(this.element);
710
+ var headerHeight = this.element.find('.wc-header').outerHeight();
711
+ var navHeight = this.element.find('.wc-toolbar').outerHeight();
712
+ var scrollContainerHeight = Math.max(calendarHeight - navHeight - headerHeight, options.minBodyHeight);
713
+ var timeslotHeight = this.element.find('.wc-time-slots').outerHeight();
714
+ this.element.find('.wc-scrollable-grid').height(scrollContainerHeight);
715
+ if (timeslotHeight <= scrollContainerHeight) {
716
+ this.element.find('.wc-scrollbar-shim').width(0);
717
+ }
718
+ else {
719
+ this.element.find('.wc-scrollbar-shim').width(this._findScrollBarWidth());
720
+ }
721
+ this._trigger('resize', this.element);
722
+ }
723
+ },
724
+
725
+ _findScrollBarWidth: function() {
726
+ var parent = $('<div style="width:50px;height:50px;overflow:auto"><div/></div>').appendTo('body');
727
+ var child = parent.children();
728
+ var width = child.innerWidth() - child.height(99).innerWidth();
729
+ parent.remove();
730
+ return width || /* default to 16 that is the average */ 16;
731
+ },
732
+
733
+ /*
734
+ * configure calendar interaction events that are able to use event
735
+ * delegation for greater efficiency
736
+ */
737
+ _setupEventDelegation: function() {
738
+ var self = this;
739
+ var options = this.options;
740
+ this.element.click(function(event) {
741
+ var $target = $(event.target),
742
+ freeBusyManager;
743
+ if ($target.data('preventClick')) {
744
+ return;
745
+ }
746
+ var $calEvent = $target.hasClass('wc-cal-event') ? $target : $target.parents('.wc-cal-event');
747
+ if ($calEvent.length) {
748
+ freeBusyManager = self.getFreeBusyManagerForEvent($calEvent.data('calEvent'));
749
+ if (options.allowEventDelete && $target.hasClass('wc-cal-event-delete')) {
750
+ options.eventDelete($calEvent.data('calEvent'), $calEvent, freeBusyManager, self.element, event);
751
+ }else{
752
+ options.eventClick($calEvent.data('calEvent'), $calEvent, freeBusyManager, self.element, event);
753
+ }
754
+ }
755
+ }).mouseover(function(event) {
756
+ var $target = $(event.target);
757
+
758
+ if (self._isDraggingOrResizing($target)) {
759
+ return;
760
+ }
761
+
762
+ if ($target.hasClass('wc-cal-event')) {
763
+ options.eventMouseover($target.data('calEvent'), $target, event);
764
+ }
765
+ }).mouseout(function(event) {
766
+ var $target = $(event.target);
767
+ if (self._isDraggingOrResizing($target)) {
768
+ return;
769
+ }
770
+ if ($target.hasClass('wc-cal-event')) {
771
+ if ($target.data('sizing')) { return;}
772
+ options.eventMouseout($target.data('calEvent'), $target, event);
773
+ }
774
+ });
775
+ },
776
+
777
+ /*
778
+ * check if a ui draggable or resizable is currently being dragged or resized
779
+ */
780
+ _isDraggingOrResizing: function($target) {
781
+ return $target.hasClass('ui-draggable-dragging') || $target.hasClass('ui-resizable-resizing');
782
+ },
783
+
784
+ /*
785
+ * Render the main calendar layout
786
+ */
787
+ _renderCalendar: function() {
788
+ var $calendarContainer, $weekDayColumns;
789
+ var self = this;
790
+ var options = this.options;
791
+
792
+ $calendarContainer = $('<div class=\"ui-widget wc-container\">').appendTo(self.element);
793
+
794
+ //render the different parts
795
+ // nav links
796
+ self._renderCalendarButtons($calendarContainer);
797
+ // header
798
+ self._renderCalendarHeader($calendarContainer);
799
+ // body
800
+ self._renderCalendarBody($calendarContainer);
801
+
802
+ $weekDayColumns = $calendarContainer.find('.wc-day-column-inner');
803
+ $weekDayColumns.each(function(i, val) {
804
+ if (!options.readonly) {
805
+ self._addDroppableToWeekDay($(this));
806
+ if (options.allowEventCreation) {
807
+ self._setupEventCreationForWeekDay($(this));
808
+ }
809
+ }
810
+ });
811
+ },
812
+
813
+ /**
814
+ * render the nav buttons on top of the calendar
815
+ */
816
+ _renderCalendarButtons: function($calendarContainer) {
817
+ var self = this, options = this.options;
818
+ if (options.buttons) {
819
+ var calendarNavHtml = '';
820
+
821
+ calendarNavHtml += '<div class=\"ui-widget-header wc-toolbar\">';
822
+ calendarNavHtml += '<div class=\"wc-display\"></div>';
823
+ calendarNavHtml += '<div class=\"wc-nav\">';
824
+ calendarNavHtml += '<button class=\"wc-prev\">' + options.buttonText.lastWeek + '</button>';
825
+ calendarNavHtml += '<button class=\"wc-today\">' + options.buttonText.today + '</button>';
826
+ calendarNavHtml += '<button class=\"wc-next\">' + options.buttonText.nextWeek + '</button>';
827
+ calendarNavHtml += '</div>';
828
+ calendarNavHtml += '<h1 class=\"wc-title\"></h1>';
829
+ calendarNavHtml += '</div>';
830
+
831
+ $(calendarNavHtml).appendTo($calendarContainer);
832
+
833
+ $calendarContainer.find('.wc-nav .wc-today')
834
+ .button({
835
+ icons: {primary: 'ui-icon-home'}})
836
+ .click(function() {
837
+ self.today();
838
+ return false;
839
+ });
840
+
841
+ $calendarContainer.find('.wc-nav .wc-prev')
842
+ .button({
843
+ text: false,
844
+ icons: {primary: 'ui-icon-seek-prev'}})
845
+ .click(function() {
846
+ self.element.weekCalendar('prev');
847
+ return false;
848
+ });
849
+
850
+ $calendarContainer.find('.wc-nav .wc-next')
851
+ .button({
852
+ text: false,
853
+ icons: {primary: 'ui-icon-seek-next'}})
854
+ .click(function() {
855
+ self.element.weekCalendar('next');
856
+ return false;
857
+ });
858
+
859
+ // now add buttons to switch display
860
+ if (this.options.switchDisplay && $.isPlainObject(this.options.switchDisplay)) {
861
+ var $container = $calendarContainer.find('.wc-display');
862
+ $.each(this.options.switchDisplay, function(label, option) {
863
+ var _id = 'wc-switch-display-' + option;
864
+ var _input = $('<input type="radio" id="' + _id + '" name="wc-switch-display" class="wc-switch-display"/>');
865
+ var _label = $('<label for="' + _id + '"></label>');
866
+ _label.html(label);
867
+ _input.val(option);
868
+ if (parseInt(self.options.daysToShow, 10) === parseInt(option, 10)) {
869
+ _input.attr('checked', 'checked');
870
+ }
871
+ $container
872
+ .append(_input)
873
+ .append(_label);
874
+ });
875
+ $container.find('input').change(function() {
876
+ self.setDaysToShow(parseInt($(this).val(), 10));
877
+ });
878
+ }
879
+ $calendarContainer.find('.wc-nav, .wc-display').buttonset();
880
+ var _height = $calendarContainer.find('.wc-nav').outerHeight();
881
+ $calendarContainer.find('.wc-title')
882
+ .height(_height)
883
+ .css('line-height', _height + 'px');
884
+ }else{
885
+ var calendarNavHtml = '';
886
+ calendarNavHtml += '<div class=\"ui-widget-header wc-toolbar\">';
887
+ calendarNavHtml += '<h1 class=\"wc-title\"></h1>';
888
+ calendarNavHtml += '</div>';
889
+ $(calendarNavHtml).appendTo($calendarContainer);
890
+
891
+ }
892
+ },
893
+
894
+ /**
895
+ * render the calendar header, including date and user header
896
+ */
897
+ _renderCalendarHeader: function($calendarContainer) {
898
+ var self = this, options = this.options,
899
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
900
+ rowspan = '', colspan = '', calendarHeaderHtml;
901
+
902
+ if (showAsSeparatedUser) {
903
+ rowspan = ' rowspan=\"2\"';
904
+ colspan = ' colspan=\"' + options.users.length + '\" ';
905
+ }
906
+
907
+ //first row
908
+ calendarHeaderHtml = '<div class=\"ui-widget-content wc-header\">';
909
+ calendarHeaderHtml += '<table><tbody><tr><td class=\"wc-time-column-header\"></td>';
910
+ for (var i = 1; i <= options.daysToShow; i++) {
911
+ calendarHeaderHtml += '<td class=\"wc-day-column-header wc-day-' + i + '\"' + colspan + '></td>';
912
+ }
913
+ calendarHeaderHtml += '<td class=\"wc-scrollbar-shim\"' + rowspan + '></td></tr>';
914
+
915
+ //users row
916
+ if (showAsSeparatedUser) {
917
+ calendarHeaderHtml += '<tr><td class=\"wc-time-column-header\"></td>';
918
+ var uLength = options.users.length,
919
+ _headerClass = '';
920
+
921
+ for (var i = 1; i <= options.daysToShow; i++) {
922
+ for (var j = 0; j < uLength; j++) {
923
+ _headerClass = [];
924
+ if (j == 0) {
925
+ _headerClass.push('wc-day-column-first');
926
+ }
927
+ if (j == uLength - 1) {
928
+ _headerClass.push('wc-day-column-last');
929
+ }
930
+ if (!_headerClass.length) {
931
+ _headerClass = 'wc-day-column-middle';
932
+ }
933
+ else {
934
+ _headerClass = _headerClass.join(' ');
935
+ }
936
+ calendarHeaderHtml += '<td class=\"' + _headerClass + ' wc-user-header wc-day-' + i + ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
937
+ // calendarHeaderHtml+= "<div class=\"wc-user-header wc-day-" + i + " wc-user-" + self._getUserIdFromIndex(j) +"\" >";
938
+ calendarHeaderHtml += self._getUserName(j);
939
+ // calendarHeaderHtml+= "</div>";
940
+ calendarHeaderHtml += '</td>';
941
+ }
942
+ }
943
+ calendarHeaderHtml += '</tr>';
944
+ }
945
+ //close the header
946
+ calendarHeaderHtml += '</tbody></table></div>';
947
+
948
+ $(calendarHeaderHtml).appendTo($calendarContainer);
949
+ },
950
+
951
+ /**
952
+ * render the calendar body.
953
+ * Calendar body is composed of several distinct parts.
954
+ * Each part is displayed in a separated row to ease rendering.
955
+ * for further explanations, see each part rendering function.
956
+ */
957
+ _renderCalendarBody: function($calendarContainer) {
958
+ var self = this, options = this.options,
959
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
960
+ $calendarBody, $calendarTableTbody;
961
+ // create the structure
962
+ $calendarBody = '<div class=\"wc-scrollable-grid\">';
963
+ $calendarBody += '<table class=\"wc-time-slots\">';
964
+ $calendarBody += '<tbody>';
965
+ $calendarBody += '</tbody>';
966
+ $calendarBody += '</table>';
967
+ $calendarBody += '</div>';
968
+ $calendarBody = $($calendarBody);
969
+ $calendarTableTbody = $calendarBody.find('tbody');
970
+
971
+ self._renderCalendarBodyTimeSlots($calendarTableTbody);
972
+ self._renderCalendarBodyOddEven($calendarTableTbody);
973
+ self._renderCalendarBodyFreeBusy($calendarTableTbody);
974
+ self._renderCalendarBodyEvents($calendarTableTbody);
975
+
976
+ $calendarBody.appendTo($calendarContainer);
977
+
978
+ //set the column height
979
+ $calendarContainer.find('.wc-full-height-column').height(options.timeslotHeight * options.timeslotsPerDay);
980
+ //set the timeslot height
981
+ $calendarContainer.find('.wc-time-slot').height(options.timeslotHeight - 1); //account for border
982
+ //init the time row header height
983
+ /**
984
+ TODO if total height for an hour is less than 11px, there is a display problem.
985
+ Find a way to handle it
986
+ */
987
+ $calendarContainer.find('.wc-time-header-cell').css({
988
+ height: (options.timeslotHeight * options.timeslotsPerHour) - 11,
989
+ padding: 5
990
+ });
991
+ //add the user data to every impacted column
992
+ if (showAsSeparatedUser) {
993
+ for (var i = 0, uLength = options.users.length; i < uLength; i++) {
994
+ $calendarContainer.find('.wc-user-' + self._getUserIdFromIndex(i))
995
+ .data('wcUser', options.users[i])
996
+ .data('wcUserIndex', i)
997
+ .data('wcUserId', self._getUserIdFromIndex(i));
998
+ }
999
+ }
1000
+ },
1001
+
1002
+ /**
1003
+ * render the timeslots separation
1004
+ */
1005
+ _renderCalendarBodyTimeSlots: function($calendarTableTbody) {
1006
+ var options = this.options,
1007
+ renderRow, i, j,
1008
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
1009
+ start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
1010
+ end = (options.businessHours.limitDisplay ? options.businessHours.end : 24),
1011
+ rowspan = 1;
1012
+
1013
+ //calculate the rowspan
1014
+ if (options.displayOddEven) { rowspan += 1; }
1015
+ if (options.displayFreeBusys) { rowspan += 1; }
1016
+ if (rowspan > 1) {
1017
+ rowspan = ' rowspan=\"' + rowspan + '\"';
1018
+ }
1019
+ else {
1020
+ rowspan = '';
1021
+ }
1022
+
1023
+ renderRow = '<tr class=\"wc-grid-row-timeslot\">';
1024
+ renderRow += '<td class=\"wc-grid-timeslot-header\"' + rowspan + '></td>';
1025
+ renderRow += '<td colspan=\"' + options.daysToShow * (showAsSeparatedUser ? options.users.length : 1) + '\">';
1026
+ renderRow += '<div class=\"wc-no-height-wrapper wc-time-slot-wrapper\">';
1027
+ renderRow += '<div class=\"wc-time-slots\">';
1028
+
1029
+ for (i = start; i < end; i++) {
1030
+ for (j = 0; j < options.timeslotsPerHour - 1; j++) {
1031
+ renderRow += '<div class=\"wc-time-slot\"></div>';
1032
+ }
1033
+ renderRow += '<div class=\"wc-time-slot wc-hour-end\"></div>';
1034
+ }
1035
+
1036
+ renderRow += '</div>';
1037
+ renderRow += '</div>';
1038
+ renderRow += '</td>';
1039
+ renderRow += '</tr>';
1040
+
1041
+ $(renderRow).appendTo($calendarTableTbody);
1042
+ },
1043
+
1044
+ /**
1045
+ * render the odd even columns
1046
+ */
1047
+ _renderCalendarBodyOddEven: function($calendarTableTbody) {
1048
+ if (this.options.displayOddEven) {
1049
+ var options = this.options,
1050
+ renderRow = '<tr class=\"wc-grid-row-oddeven\">',
1051
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
1052
+ oddEven,
1053
+ // let's take advantage of the jquery ui framework
1054
+ oddEvenClasses = {'odd': 'wc-column-odd', 'even': 'ui-state-hover wc-column-even'};
1055
+
1056
+ //now let's display oddEven placeholders
1057
+ for (var i = 1; i <= options.daysToShow; i++) {
1058
+ if (!showAsSeparatedUser) {
1059
+ oddEven = (oddEven == 'odd' ? 'even' : 'odd');
1060
+ renderRow += '<td class=\"wc-day-column day-' + i + '\">';
1061
+ renderRow += '<div class=\"wc-no-height-wrapper wc-oddeven-wrapper\">';
1062
+ renderRow += '<div class=\"wc-full-height-column ' + oddEvenClasses[oddEven] + '\"></div>';
1063
+ renderRow += '</div>';
1064
+ renderRow += '</td>';
1065
+ }
1066
+ else {
1067
+ var uLength = options.users.length;
1068
+ for (var j = 0; j < uLength; j++) {
1069
+ oddEven = (oddEven == 'odd' ? 'even' : 'odd');
1070
+ renderRow += '<td class=\"wc-day-column day-' + i + '\">';
1071
+ renderRow += '<div class=\"wc-no-height-wrapper wc-oddeven-wrapper\">';
1072
+ renderRow += '<div class=\"wc-full-height-column ' + oddEvenClasses[oddEven] + '\" ></div>';
1073
+ renderRow += '</div>';
1074
+ renderRow += '</td>';
1075
+ }
1076
+ }
1077
+ }
1078
+ renderRow += '</tr>';
1079
+
1080
+ $(renderRow).appendTo($calendarTableTbody);
1081
+ }
1082
+ },
1083
+
1084
+ /**
1085
+ * render the freebusy placeholders
1086
+ */
1087
+ _renderCalendarBodyFreeBusy: function($calendarTableTbody) {
1088
+ if (this.options.displayFreeBusys) {
1089
+ var self = this, options = this.options,
1090
+ renderRow = '<tr class=\"wc-grid-row-freebusy\">',
1091
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
1092
+ renderRow += '</td>';
1093
+
1094
+ //now let's display freebusy placeholders
1095
+ for (var i = 1; i <= options.daysToShow; i++) {
1096
+ if (options.displayFreeBusys) {
1097
+ if (!showAsSeparatedUser) {
1098
+ renderRow += '<td class=\"wc-day-column day-' + i + '\">';
1099
+ renderRow += '<div class=\"wc-no-height-wrapper wc-freebusy-wrapper\">';
1100
+ renderRow += '<div class=\"wc-full-height-column wc-column-freebusy wc-day-' + i + '\"></div>';
1101
+ renderRow += '</div>';
1102
+ renderRow += '</td>';
1103
+ }
1104
+ else {
1105
+ var uLength = options.users.length;
1106
+ for (var j = 0; j < uLength; j++) {
1107
+ renderRow += '<td class=\"wc-day-column day-' + i + '\">';
1108
+ renderRow += '<div class=\"wc-no-height-wrapper wc-freebusy-wrapper\">';
1109
+ renderRow += '<div class=\"wc-full-height-column wc-column-freebusy wc-day-' + i;
1110
+ renderRow += ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
1111
+ renderRow += '</div>';
1112
+ renderRow += '</div>';
1113
+ renderRow += '</td>';
1114
+ }
1115
+ }
1116
+ }
1117
+ }
1118
+
1119
+ renderRow += '</tr>';
1120
+
1121
+ $(renderRow).appendTo($calendarTableTbody);
1122
+ }
1123
+ },
1124
+
1125
+ /**
1126
+ * render the calendar body for event placeholders
1127
+ */
1128
+ _renderCalendarBodyEvents: function($calendarTableTbody) {
1129
+ var self = this, options = this.options,
1130
+ renderRow,
1131
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
1132
+ start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
1133
+ end = (options.businessHours.limitDisplay ? options.businessHours.end : 24);
1134
+ renderRow = '<tr class=\"wc-grid-row-events\">';
1135
+ renderRow += '<td class=\"wc-grid-timeslot-header\">';
1136
+ for (var i = start; i < end; i++) {
1137
+ var bhClass = (options.businessHours.start <= i && options.businessHours.end > i) ? 'ui-state-active wc-business-hours' : 'ui-state-default';
1138
+ renderRow += '<div class=\"wc-hour-header ' + bhClass + '\">';
1139
+ if (options.use24Hour) {
1140
+ renderRow += '<div class=\"wc-time-header-cell\">' + self._24HourForIndex(i) + '</div>';
1141
+ }
1142
+ else {
1143
+ renderRow += '<div class=\"wc-time-header-cell\">' + self._hourForIndex(i) + '<span class=\"wc-am-pm\">' + self._amOrPm(i) + '</span></div>';
1144
+ }
1145
+ renderRow += '</div>';
1146
+ }
1147
+ renderRow += '</td>';
1148
+
1149
+ //now let's display events placeholders
1150
+ var _columnBaseClass = 'ui-state-default wc-day-column';
1151
+ for (var i = 1; i <= options.daysToShow; i++) {
1152
+ if (!showAsSeparatedUser) {
1153
+ renderRow += '<td class=\"' + _columnBaseClass + ' wc-day-column-first wc-day-column-last day-' + i + '\">';
1154
+ renderRow += '<div class=\"wc-full-height-column wc-day-column-inner day-' + i + '\"></div>';
1155
+ renderRow += '</td>';
1156
+ }
1157
+ else {
1158
+ var uLength = options.users.length;
1159
+ var columnclass;
1160
+ for (var j = 0; j < uLength; j++) {
1161
+ columnclass = [];
1162
+ if (j == 0) {
1163
+ columnclass.push('wc-day-column-first');
1164
+ }
1165
+ if (j == uLength - 1) {
1166
+ columnclass.push('wc-day-column-last');
1167
+ }
1168
+ if (!columnclass.length) {
1169
+ columnclass = 'wc-day-column-middle';
1170
+ }
1171
+ else {
1172
+ columnclass = columnclass.join(' ');
1173
+ }
1174
+ renderRow += '<td class=\"' + _columnBaseClass + ' ' + columnclass + ' day-' + i + '\">';
1175
+ renderRow += '<div class=\"wc-full-height-column wc-day-column-inner day-' + i;
1176
+ renderRow += ' wc-user-' + self._getUserIdFromIndex(j) + '\">';
1177
+ renderRow += '</div>';
1178
+ renderRow += '</td>';
1179
+ }
1180
+ }
1181
+ }
1182
+
1183
+ renderRow += '</tr>';
1184
+
1185
+ $(renderRow).appendTo($calendarTableTbody);
1186
+ },
1187
+
1188
+ /*
1189
+ * setup mouse events for capturing new events
1190
+ */
1191
+ _setupEventCreationForWeekDay: function($weekDay) {
1192
+ var self = this;
1193
+ var options = this.options;
1194
+ $weekDay.mousedown(function(event) {
1195
+ var $target = $(event.target);
1196
+ if ($target.hasClass('wc-day-column-inner')) {
1197
+
1198
+ var $newEvent = $('<div class=\"wc-cal-event wc-new-cal-event wc-new-cal-event-creating\"></div>');
1199
+
1200
+ $newEvent.css({lineHeight: (options.timeslotHeight - 2) + 'px', fontSize: (options.timeslotHeight / 2) + 'px'});
1201
+ $target.append($newEvent);
1202
+
1203
+ var columnOffset = $target.offset().top;
1204
+ var clickY = event.pageY - columnOffset;
1205
+ var clickYRounded = (clickY - (clickY % options.timeslotHeight)) / options.timeslotHeight;
1206
+ var topPosition = clickYRounded * options.timeslotHeight;
1207
+ $newEvent.css({top: topPosition});
1208
+
1209
+ if (!options.preventDragOnEventCreation) {
1210
+ $target.bind('mousemove.newevent', function(event) {
1211
+ $newEvent.show();
1212
+ $newEvent.addClass('ui-resizable-resizing');
1213
+ var height = Math.round(event.pageY - columnOffset - topPosition);
1214
+ var remainder = height % options.timeslotHeight;
1215
+ //snap to closest timeslot
1216
+ if (remainder < 0) {
1217
+ var useHeight = height - remainder;
1218
+ $newEvent.css('height', useHeight < options.timeslotHeight ? options.timeslotHeight : useHeight);
1219
+ } else {
1220
+ $newEvent.css('height', height + (options.timeslotHeight - remainder));
1221
+ }
1222
+ }).mouseup(function() {
1223
+ $target.unbind('mousemove.newevent');
1224
+ $newEvent.addClass('ui-corner-all');
1225
+ });
1226
+ }
1227
+ }
1228
+
1229
+ }).mouseup(function(event) {
1230
+ var $target = $(event.target);
1231
+
1232
+ var $weekDay = $target.closest('.wc-day-column-inner');
1233
+ var $newEvent = $weekDay.find('.wc-new-cal-event-creating');
1234
+
1235
+ if ($newEvent.length) {
1236
+ var createdFromSingleClick = !$newEvent.hasClass('ui-resizable-resizing');
1237
+
1238
+ //if even created from a single click only, default height
1239
+ if (createdFromSingleClick) {
1240
+ $newEvent.css({height: options.timeslotHeight * options.defaultEventLength}).show();
1241
+ }
1242
+ var top = parseInt($newEvent.css('top'));
1243
+ var eventDuration = self._getEventDurationFromPositionedEventElement($weekDay, $newEvent, top);
1244
+
1245
+ $newEvent.remove();
1246
+ var newCalEvent = {start: eventDuration.start, end: eventDuration.end, title: options.newEventText};
1247
+ var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
1248
+
1249
+ if (showAsSeparatedUser) {
1250
+ newCalEvent = self._setEventUserId(newCalEvent, $weekDay.data('wcUserId'));
1251
+ }
1252
+ else if (!options.showAsSeparateUsers && options.users && options.users.length == 1) {
1253
+ newCalEvent = self._setEventUserId(newCalEvent, self._getUserIdFromIndex(0));
1254
+ }
1255
+
1256
+ var freeBusyManager = self.getFreeBusyManagerForEvent(newCalEvent);
1257
+
1258
+ var $renderedCalEvent = self._renderEvent(newCalEvent, $weekDay);
1259
+
1260
+ if (!options.allowCalEventOverlap) {
1261
+ self._adjustForEventCollisions($weekDay, $renderedCalEvent, newCalEvent, newCalEvent);
1262
+ self._positionEvent($weekDay, $renderedCalEvent);
1263
+ } else {
1264
+ self._adjustOverlappingEvents($weekDay);
1265
+ }
1266
+
1267
+ var proceed = self._trigger('beforeEventNew', event, {
1268
+ 'calEvent': newCalEvent,
1269
+ 'createdFromSingleClick': createdFromSingleClick,
1270
+ 'calendar': self.element
1271
+ });
1272
+ if (proceed) {
1273
+ options.eventNew(newCalEvent, $renderedCalEvent, freeBusyManager, self.element, event);
1274
+ }
1275
+ else {
1276
+ $($renderedCalEvent).remove();
1277
+ }
1278
+ }
1279
+ });
1280
+ },
1281
+
1282
+ /*
1283
+ * load calendar events for the week based on the date provided
1284
+ */
1285
+ _loadCalEvents: function(dateWithinWeek) {
1286
+
1287
+ var date, weekStartDate, weekEndDate, $weekDayColumns;
1288
+ var self = this;
1289
+ var options = this.options;
1290
+ date = this._fixMinMaxDate(dateWithinWeek || options.date);
1291
+ // if date is not provided
1292
+ // or was not set
1293
+ // or is different than old one
1294
+ if ((!date || !date.getTime) ||
1295
+ (!options.date || !options.date.getTime) ||
1296
+ date.getTime() != options.date.getTime()
1297
+ ) {
1298
+ // trigger the changedate event
1299
+ this._trigger('changedate', this.element, date);
1300
+ }
1301
+ this.options.date = date;
1302
+ weekStartDate = self._dateFirstDayOfWeek(date);
1303
+ weekEndDate = self._dateLastMilliOfWeek(date);
1304
+
1305
+ options.calendarBeforeLoad(self.element);
1306
+
1307
+ self.element.data('startDate', weekStartDate);
1308
+ self.element.data('endDate', weekEndDate);
1309
+
1310
+ $weekDayColumns = self.element.find('.wc-day-column-inner');
1311
+
1312
+ self._updateDayColumnHeader($weekDayColumns);
1313
+
1314
+ //load events by chosen means
1315
+ if (typeof options.data == 'string') {
1316
+ if (options.loading) {
1317
+ options.loading(true);
1318
+ }
1319
+ if (_currentAjaxCall) {
1320
+ // first abort current request.
1321
+ if (!_jQuery14OrLower) {
1322
+ _currentAjaxCall.abort();
1323
+ } else {
1324
+ // due to the fact that jquery 1.4 does not detect a request was
1325
+ // aborted, we need to replace the onreadystatechange and
1326
+ // execute the "complete" callback.
1327
+ _currentAjaxCall.onreadystatechange = null;
1328
+ _currentAjaxCall.abort();
1329
+ _currentAjaxCall = null;
1330
+ if (options.loading) {
1331
+ options.loading(false);
1332
+ }
1333
+ }
1334
+ }
1335
+ var jsonOptions = self._getJsonOptions();
1336
+ jsonOptions[options.startParam || 'start'] = Math.round(weekStartDate.getTime() / 1000);
1337
+ jsonOptions[options.endParam || 'end'] = Math.round(weekEndDate.getTime() / 1000);
1338
+ _currentAjaxCall = $.ajax({
1339
+ url: options.data,
1340
+ data: jsonOptions,
1341
+ dataType: 'json',
1342
+ error: function(XMLHttpRequest, textStatus, errorThrown) {
1343
+ // only prevent error with jQuery 1.5
1344
+ // see issue #34. thanks to dapplebeforedawn
1345
+ // (https://github.com/themouette/jquery-week-calendar/issues#issue/34)
1346
+ // for 1.5+, aborted request mean errorThrown == 'abort'
1347
+ // for prior version it means !errorThrown && !XMLHttpRequest.status
1348
+ // fixes #55
1349
+ if (errorThrown != 'abort' && XMLHttpRequest.status != 0) {
1350
+ alert('unable to get data, error:' + textStatus);
1351
+ }
1352
+ },
1353
+ success: function(data) {
1354
+ self._renderEvents(data, $weekDayColumns);
1355
+ },
1356
+ complete: function() {
1357
+ _currentAjaxCall = null;
1358
+ if (options.loading) {
1359
+ options.loading(false);
1360
+ }
1361
+ }
1362
+ });
1363
+ }
1364
+ else if ($.isFunction(options.data)) {
1365
+ options.data(weekStartDate, weekEndDate,
1366
+ function(data) {
1367
+ self._renderEvents(data, $weekDayColumns);
1368
+ });
1369
+ }
1370
+ else if (options.data) {
1371
+ self._renderEvents(options.data, $weekDayColumns);
1372
+ }
1373
+
1374
+ self._disableTextSelect($weekDayColumns);
1375
+
1376
+
1377
+ },
1378
+
1379
+ /*
1380
+ * update the display of each day column header based on the calendar week
1381
+ */
1382
+ _updateDayColumnHeader: function($weekDayColumns) {
1383
+ var self = this;
1384
+ var options = this.options;
1385
+ var currentDay = self._cloneDate(self.element.data('startDate'));
1386
+ var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
1387
+ var todayClass = 'ui-state-active wc-today';
1388
+
1389
+ self.element.find('.wc-header td.wc-day-column-header').each(function(i, val) {
1390
+ $(this).html(self._getHeaderDate(currentDay));
1391
+ if (self._isToday(currentDay)) {
1392
+ $(this).addClass(todayClass);
1393
+ } else {
1394
+ $(this).removeClass(todayClass);
1395
+ }
1396
+ currentDay = self._addDays(currentDay, 1);
1397
+
1398
+ });
1399
+
1400
+ currentDay = self._cloneDate(self.element.data('startDate'));
1401
+ if (showAsSeparatedUser)
1402
+ {
1403
+ self.element.find('.wc-header td.wc-user-header').each(function(i, val) {
1404
+ if (self._isToday(currentDay)) {
1405
+ $(this).addClass(todayClass);
1406
+ } else {
1407
+ $(this).removeClass(todayClass);
1408
+ }
1409
+ currentDay = ((i + 1) % options.users.length) ? currentDay : self._addDays(currentDay, 1);
1410
+ });
1411
+ }
1412
+
1413
+ currentDay = self._cloneDate(self.element.data('startDate'));
1414
+
1415
+ $weekDayColumns.each(function(i, val) {
1416
+
1417
+ $(this).data('startDate', self._cloneDate(currentDay));
1418
+ $(this).data('endDate', new Date(currentDay.getTime() + (MILLIS_IN_DAY)));
1419
+ if (self._isToday(currentDay)) {
1420
+ $(this).parent()
1421
+ .addClass(todayClass)
1422
+ .removeClass('ui-state-default');
1423
+ } else {
1424
+ $(this).parent()
1425
+ .removeClass(todayClass)
1426
+ .addClass('ui-state-default');
1427
+ }
1428
+
1429
+ if (!showAsSeparatedUser || !((i + 1) % options.users.length)) {
1430
+ currentDay = self._addDays(currentDay, 1);
1431
+ }
1432
+ });
1433
+
1434
+ //now update the freeBusy placeholders
1435
+ if (options.displayFreeBusys) {
1436
+ currentDay = self._cloneDate(self.element.data('startDate'));
1437
+ self.element.find('.wc-grid-row-freebusy .wc-column-freebusy').each(function(i, val) {
1438
+ $(this).data('startDate', self._cloneDate(currentDay));
1439
+ $(this).data('endDate', new Date(currentDay.getTime() + (MILLIS_IN_DAY)));
1440
+ if (!showAsSeparatedUser || !((i + 1) % options.users.length)) {
1441
+ currentDay = self._addDays(currentDay, 1);
1442
+ }
1443
+ });
1444
+ }
1445
+
1446
+ //now update the calendar title
1447
+ if (this.options.title && this.options.title.length) {
1448
+ var _date = this.options.date,
1449
+ _start = self._cloneDate(self.element.data('startDate')),
1450
+ _end = self._dateLastDayOfWeek(new Date(this._cloneDate(self.element.data('endDate')).getTime() - (MILLIS_IN_DAY))),
1451
+ _title = this._getCalendarTitle();
1452
+ _title = _title.split('%start%').join(self._formatDate(_start, options.dateFormat));
1453
+ _title = _title.split('%end%').join(self._formatDate(_end, options.dateFormat));
1454
+ _title = _title.split('%date%').join(self._formatDate(_date, options.dateFormat));
1455
+ $('.wc-toolbar .wc-title', self.element).html(_title);
1456
+ }
1457
+ //self._clearFreeBusys();
1458
+ },
1459
+
1460
+ /*
1461
+ * gets the calendar title options
1462
+ */
1463
+ _getCalendarTitle: function() {
1464
+ if ($.isFunction(this.options.title)) {
1465
+ return this.options.title(this.options.daysToShow);
1466
+ }
1467
+ return this.options.title;
1468
+ },
1469
+
1470
+ /*
1471
+ * Render the events into the calendar
1472
+ */
1473
+ _renderEvents: function(data, $weekDayColumns) {
1474
+ var self = this;
1475
+ var options = this.options;
1476
+ var eventsToRender;
1477
+
1478
+ if (data.options) {
1479
+ var updateLayout = false;
1480
+ //update options
1481
+ $.each(data.options, function(key, value) {
1482
+ if (value !== options[key]) {
1483
+ options[key] = value;
1484
+ updateLayout = updateLayout || $.ui.weekCalendar.updateLayoutOptions[key];
1485
+ }
1486
+ });
1487
+
1488
+ self._computeOptions();
1489
+
1490
+ if (updateLayout) {
1491
+ var hour = self._getCurrentScrollHour();
1492
+ self.element.empty();
1493
+ self._renderCalendar();
1494
+ $weekDayColumns = self.element.find('.wc-time-slots .wc-day-column-inner');
1495
+ self._updateDayColumnHeader($weekDayColumns);
1496
+ self._resizeCalendar();
1497
+ self._scrollToHour(hour, false);
1498
+ }
1499
+ }
1500
+ this._clearCalendar();
1501
+
1502
+ if ($.isArray(data)) {
1503
+ eventsToRender = self._cleanEvents(data);
1504
+ } else if (data.events) {
1505
+ eventsToRender = self._cleanEvents(data.events);
1506
+ //render the freebusys
1507
+ self._renderFreeBusys(data);
1508
+ }
1509
+ $.each(eventsToRender, function(i, calEvent) {
1510
+ //render a multi day event as various event :
1511
+ //thanks to http://github.com/fbeauchamp/jquery-week-calendar
1512
+ var initialStart = new Date(calEvent.start);
1513
+ var initialEnd = new Date(calEvent.end);
1514
+ var maxHour = self.options.businessHours.limitDisplay ? self.options.businessHours.end : 24;
1515
+ var minHour = self.options.businessHours.limitDisplay ? self.options.businessHours.start : 0;
1516
+ var start = new Date(initialStart);
1517
+ var startDate = self._formatDate(start, 'Ymd');
1518
+ var endDate = self._formatDate(initialEnd, 'Ymd');
1519
+ var $weekDay;
1520
+ var isMultiday = false;
1521
+
1522
+ while (startDate < endDate) {
1523
+ calEvent.start = start;
1524
+ //end of this virual calEvent is set to the end of the day
1525
+ calEvent.end.setFullYear(start.getFullYear());
1526
+ calEvent.end.setDate(start.getDate());
1527
+ calEvent.end.setMonth(start.getMonth());
1528
+ calEvent.end.setHours(maxHour);
1529
+ calEvent.end.setMinutes(0);
1530
+ calEvent.end.setSeconds(0);
1531
+ if (($weekDay = self._findWeekDayForEvent(calEvent, $weekDayColumns))) {
1532
+ self._renderEvent(calEvent, $weekDay);
1533
+ }
1534
+ //start is set to the begin of the new day
1535
+ start.setDate(start.getDate() + 1);
1536
+ start.setHours(minHour);
1537
+ start.setMinutes(0);
1538
+ start.setSeconds(0);
1539
+ startDate = self._formatDate(start, 'Ymd');
1540
+ isMultiday = true;
1541
+ }
1542
+ if (start <= initialEnd) {
1543
+ calEvent.start = start;
1544
+ calEvent.end = initialEnd;
1545
+ if (((isMultiday && calEvent.start.getTime() != calEvent.end.getTime()) || !isMultiday) && ($weekDay = self._findWeekDayForEvent(calEvent, $weekDayColumns))) {
1546
+ self._renderEvent(calEvent, $weekDay);
1547
+ }
1548
+ }
1549
+
1550
+ //put back the initial start date
1551
+ calEvent.start = initialStart;
1552
+ });
1553
+
1554
+ $weekDayColumns.each(function() {
1555
+ self._adjustOverlappingEvents($(this));
1556
+ });
1557
+
1558
+ options.calendarAfterLoad(self.element);
1559
+
1560
+ if (!eventsToRender.length) {
1561
+ options.noEvents();
1562
+ }
1563
+
1564
+ },
1565
+
1566
+ /*
1567
+ * Render a specific event into the day provided. Assumes correct
1568
+ * day for calEvent date
1569
+ */
1570
+ _renderEvent: function(calEvent, $weekDay) {
1571
+ var self = this;
1572
+ var options = this.options;
1573
+ if (calEvent.start.getTime() > calEvent.end.getTime()) {
1574
+ return; // can't render a negative height
1575
+ }
1576
+
1577
+ var eventClass, eventHtml, $calEventList, $modifiedEvent;
1578
+
1579
+ eventClass = calEvent.id ? 'wc-cal-event' : 'wc-cal-event wc-new-cal-event';
1580
+ eventHtml = '<div class=\"' + eventClass + ' ui-corner-all\">';
1581
+ eventHtml += '<div class=\"wc-time ui-corner-top\"></div>';
1582
+ eventHtml += '<div class=\"wc-title\"></div></div>';
1583
+
1584
+ $weekDay.each(function() {
1585
+ var $calEvent = $(eventHtml);
1586
+ $modifiedEvent = options.eventRender(calEvent, $calEvent);
1587
+ $calEvent = $modifiedEvent ? $modifiedEvent.appendTo($(this)) : $calEvent.appendTo($(this));
1588
+ $calEvent.css({lineHeight: (options.textSize + 2) + 'px', fontSize: options.textSize + 'px'});
1589
+
1590
+ self._refreshEventDetails(calEvent, $calEvent);
1591
+ self._positionEvent($(this), $calEvent);
1592
+
1593
+ //add to event list
1594
+ if ($calEventList) {
1595
+ $calEventList = $calEventList.add($calEvent);
1596
+ }
1597
+ else {
1598
+ $calEventList = $calEvent;
1599
+ }
1600
+ });
1601
+ $calEventList.show();
1602
+
1603
+ if (!options.readonly && options.resizable(calEvent, $calEventList)) {
1604
+ self._addResizableToCalEvent(calEvent, $calEventList, $weekDay);
1605
+ }
1606
+ if (!options.readonly && options.draggable(calEvent, $calEventList)) {
1607
+ self._addDraggableToCalEvent(calEvent, $calEventList);
1608
+ }
1609
+ options.eventAfterRender(calEvent, $calEventList);
1610
+
1611
+ return $calEventList;
1612
+
1613
+ },
1614
+ addEvent: function() {
1615
+ return this._renderEvent.apply(this, arguments);
1616
+ },
1617
+
1618
+ _adjustOverlappingEvents: function($weekDay) {
1619
+ var self = this;
1620
+ if (self.options.allowCalEventOverlap) {
1621
+ var groupsList = self._groupOverlappingEventElements($weekDay);
1622
+ $.each(groupsList, function() {
1623
+ var curGroups = this;
1624
+ $.each(curGroups, function(groupIndex) {
1625
+ var curGroup = this;
1626
+
1627
+ // do we want events to be displayed as overlapping
1628
+ if (self.options.overlapEventsSeparate) {
1629
+ var newWidth = self.options.totalEventsWidthPercentInOneColumn / curGroups.length;
1630
+ var newLeft = groupIndex * newWidth;
1631
+ } else {
1632
+ // TODO what happens when the group has more than 10 elements
1633
+ var newWidth = self.options.totalEventsWidthPercentInOneColumn - ((curGroups.length - 1) * 10);
1634
+ var newLeft = groupIndex * 10;
1635
+ }
1636
+ $.each(curGroup, function() {
1637
+ // bring mouseovered event to the front
1638
+ if (!self.options.overlapEventsSeparate) {
1639
+ $(this).bind('mouseover.z-index', function() {
1640
+ var $elem = $(this);
1641
+ $.each(curGroup, function() {
1642
+ $(this).css({'z-index': '1'});
1643
+ });
1644
+ $elem.css({'z-index': '3'});
1645
+ });
1646
+ }
1647
+ $(this).css({width: newWidth + '%', left: newLeft + '%', right: 0});
1648
+ });
1649
+ });
1650
+ });
1651
+ }
1652
+ },
1653
+
1654
+
1655
+ /*
1656
+ * Find groups of overlapping events
1657
+ */
1658
+ _groupOverlappingEventElements: function($weekDay) {
1659
+ var $events = $weekDay.find('.wc-cal-event:visible');
1660
+ var sortedEvents = $events.sort(function(a, b) {
1661
+ return $(a).data('calEvent').start.getTime() - $(b).data('calEvent').start.getTime();
1662
+ });
1663
+
1664
+ var lastEndTime = new Date(0, 0, 0);
1665
+ var groups = [];
1666
+ var curGroups = [];
1667
+ var $curEvent;
1668
+ $.each(sortedEvents, function() {
1669
+ $curEvent = $(this);
1670
+ //checks, if the current group list is not empty, if the overlapping is finished
1671
+ if (curGroups.length > 0) {
1672
+ if (lastEndTime.getTime() <= $curEvent.data('calEvent').start.getTime()) {
1673
+ //finishes the current group list by adding it to the resulting list of groups and cleans it
1674
+
1675
+ groups.push(curGroups);
1676
+ curGroups = [];
1677
+ }
1678
+ }
1679
+
1680
+ //finds the first group to fill with the event
1681
+ for (var groupIndex = 0; groupIndex < curGroups.length; groupIndex++) {
1682
+ if (curGroups[groupIndex].length > 0) {
1683
+ //checks if the event starts after the end of the last event of the group
1684
+ if (curGroups[groupIndex][curGroups[groupIndex].length - 1].data('calEvent').end.getTime() <= $curEvent.data('calEvent').start.getTime()) {
1685
+ curGroups[groupIndex].push($curEvent);
1686
+ if (lastEndTime.getTime() < $curEvent.data('calEvent').end.getTime()) {
1687
+ lastEndTime = $curEvent.data('calEvent').end;
1688
+ }
1689
+ return;
1690
+ }
1691
+ }
1692
+ }
1693
+ //if not found, creates a new group
1694
+ curGroups.push([$curEvent]);
1695
+ if (lastEndTime.getTime() < $curEvent.data('calEvent').end.getTime()) {
1696
+ lastEndTime = $curEvent.data('calEvent').end;
1697
+ }
1698
+ });
1699
+ //adds the last groups in result
1700
+ if (curGroups.length > 0) {
1701
+ groups.push(curGroups);
1702
+ }
1703
+ return groups;
1704
+ },
1705
+
1706
+
1707
+ /*
1708
+ * find the weekday in the current calendar that the calEvent falls within
1709
+ */
1710
+ _findWeekDayForEvent: function(calEvent, $weekDayColumns) {
1711
+
1712
+ var $weekDay,
1713
+ options = this.options,
1714
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
1715
+ user_ids = this._getEventUserId(calEvent);
1716
+
1717
+ if (!$.isArray(user_ids)) {
1718
+ user_ids = [user_ids];
1719
+ }
1720
+
1721
+ $weekDayColumns.each(function(index, curDay) {
1722
+ if ($(this).data('startDate').getTime() <= calEvent.start.getTime() &&
1723
+ $(this).data('endDate').getTime() >= calEvent.end.getTime() &&
1724
+ (!showAsSeparatedUser || $.inArray($(this).data('wcUserId'), user_ids) !== -1)
1725
+ ) {
1726
+ if ($weekDay) {
1727
+ $weekDay = $weekDay.add($(curDay));
1728
+ }
1729
+ else {
1730
+ $weekDay = $(curDay);
1731
+ }
1732
+ }
1733
+ });
1734
+
1735
+ return $weekDay;
1736
+ },
1737
+
1738
+ /*
1739
+ * update the events rendering in the calendar. Add if does not yet exist.
1740
+ */
1741
+ _updateEventInCalendar: function(calEvent) {
1742
+ var self = this;
1743
+ self._cleanEvent(calEvent);
1744
+
1745
+ if (calEvent.id) {
1746
+ self.element.find('.wc-cal-event').each(function() {
1747
+ if ($(this).data('calEvent').id === calEvent.id || $(this).hasClass('wc-new-cal-event')) {
1748
+ $(this).remove();
1749
+ // return false;
1750
+ }
1751
+ });
1752
+ }
1753
+
1754
+ var $weekDays = self._findWeekDayForEvent(calEvent, self.element.find('.wc-grid-row-events .wc-day-column-inner'));
1755
+ if ($weekDays) {
1756
+ $weekDays.each(function(index, weekDay) {
1757
+ var $weekDay = $(weekDay);
1758
+ var $calEvent = self._renderEvent(calEvent, $weekDay);
1759
+ self._adjustForEventCollisions($weekDay, $calEvent, calEvent, calEvent);
1760
+ self._refreshEventDetails(calEvent, $calEvent);
1761
+ self._positionEvent($weekDay, $calEvent);
1762
+ self._adjustOverlappingEvents($weekDay);
1763
+ });
1764
+ }
1765
+ },
1766
+
1767
+ /*
1768
+ * Position the event element within the weekday based on it's start / end dates.
1769
+ */
1770
+ _positionEvent: function($weekDay, $calEvent) {
1771
+ var options = this.options;
1772
+ var calEvent = $calEvent.data('calEvent');
1773
+ var pxPerMillis = $weekDay.height() / options.millisToDisplay;
1774
+ var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
1775
+ var startMillis = this._getDSTdayShift(calEvent.start).getTime() - this._getDSTdayShift(new Date(calEvent.start.getFullYear(), calEvent.start.getMonth(), calEvent.start.getDate(), firstHourDisplayed)).getTime();
1776
+ var eventMillis = this._getDSTdayShift(calEvent.end).getTime() - this._getDSTdayShift(calEvent.start).getTime();
1777
+ var pxTop = pxPerMillis * startMillis;
1778
+ var pxHeight = pxPerMillis * eventMillis;
1779
+ //var pxHeightFallback = pxPerMillis * (60 / options.timeslotsPerHour) * 60 * 1000;
1780
+ $calEvent.css({top: pxTop, height: pxHeight || (pxPerMillis * 3600000 / options.timeslotsPerHour)});
1781
+ },
1782
+
1783
+ /*
1784
+ * Determine the actual start and end times of a calevent based on it's
1785
+ * relative position within the weekday column and the starting hour of the
1786
+ * displayed calendar.
1787
+ */
1788
+ _getEventDurationFromPositionedEventElement: function($weekDay, $calEvent, top) {
1789
+ var options = this.options;
1790
+ var startOffsetMillis = options.businessHours.limitDisplay ? options.businessHours.start * 3600000 : 0;
1791
+ var start = new Date($weekDay.data('startDate').getTime() + startOffsetMillis + Math.round(top / options.timeslotHeight) * options.millisPerTimeslot);
1792
+ var end = new Date(start.getTime() + ($calEvent.height() / options.timeslotHeight) * options.millisPerTimeslot);
1793
+ return {start: this._getDSTdayShift(start, -1), end: this._getDSTdayShift(end, -1)};
1794
+ },
1795
+
1796
+ /*
1797
+ * If the calendar does not allow event overlap, adjust the start or end date if necessary to
1798
+ * avoid overlapping of events. Typically, shortens the resized / dropped event to it's max possible
1799
+ * duration based on the overlap. If no satisfactory adjustment can be made, the event is reverted to
1800
+ * it's original location.
1801
+ */
1802
+ _adjustForEventCollisions: function($weekDay, $calEvent, newCalEvent, oldCalEvent, maintainEventDuration) {
1803
+ var options = this.options;
1804
+
1805
+ if (options.allowCalEventOverlap) {
1806
+ return;
1807
+ }
1808
+ var adjustedStart, adjustedEnd;
1809
+ var self = this;
1810
+
1811
+ $weekDay.find('.wc-cal-event').not($calEvent).each(function() {
1812
+ var currentCalEvent = $(this).data('calEvent');
1813
+
1814
+ //has been dropped onto existing event overlapping the end time
1815
+ if (newCalEvent.start.getTime() < currentCalEvent.end.getTime() &&
1816
+ newCalEvent.end.getTime() >= currentCalEvent.end.getTime()) {
1817
+
1818
+ adjustedStart = currentCalEvent.end;
1819
+ }
1820
+
1821
+
1822
+ //has been dropped onto existing event overlapping the start time
1823
+ if (newCalEvent.end.getTime() > currentCalEvent.start.getTime() &&
1824
+ newCalEvent.start.getTime() <= currentCalEvent.start.getTime()) {
1825
+
1826
+ adjustedEnd = currentCalEvent.start;
1827
+ }
1828
+ //has been dropped inside existing event with same or larger duration
1829
+ if (oldCalEvent.resizable == false ||
1830
+ (newCalEvent.end.getTime() <= currentCalEvent.end.getTime() &&
1831
+ newCalEvent.start.getTime() >= currentCalEvent.start.getTime())) {
1832
+
1833
+ adjustedStart = oldCalEvent.start;
1834
+ adjustedEnd = oldCalEvent.end;
1835
+ return false;
1836
+ }
1837
+
1838
+ });
1839
+
1840
+
1841
+ newCalEvent.start = adjustedStart || newCalEvent.start;
1842
+
1843
+ if (adjustedStart && maintainEventDuration) {
1844
+ newCalEvent.end = new Date(adjustedStart.getTime() + (oldCalEvent.end.getTime() - oldCalEvent.start.getTime()));
1845
+ self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, oldCalEvent);
1846
+ } else {
1847
+ newCalEvent.end = adjustedEnd || newCalEvent.end;
1848
+ }
1849
+
1850
+
1851
+ //reset if new cal event has been forced to zero size
1852
+ if (newCalEvent.start.getTime() >= newCalEvent.end.getTime()) {
1853
+ newCalEvent.start = oldCalEvent.start;
1854
+ newCalEvent.end = oldCalEvent.end;
1855
+ }
1856
+
1857
+ $calEvent.data('calEvent', newCalEvent);
1858
+ },
1859
+
1860
+ /*
1861
+ * Add draggable capabilities to an event
1862
+ */
1863
+ _addDraggableToCalEvent: function(calEvent, $calEvent) {
1864
+ var options = this.options;
1865
+ $calEvent.draggable({
1866
+ handle: '.wc-time',
1867
+ containment: 'div.wc-time-slots',
1868
+ snap: '.wc-day-column-inner',
1869
+ snapMode: 'inner',
1870
+ snapTolerance: options.timeslotHeight - 1,
1871
+ revert: 'invalid',
1872
+ opacity: 0.5,
1873
+ grid: [$calEvent.outerWidth() + 1, options.timeslotHeight],
1874
+ start: function(event, ui) {
1875
+ var $calEvent = ui.draggable;
1876
+ options.eventDrag(calEvent, $calEvent);
1877
+ }
1878
+ });
1879
+ },
1880
+
1881
+ /*
1882
+ * Add droppable capabilites to weekdays to allow dropping of calEvents only
1883
+ */
1884
+ _addDroppableToWeekDay: function($weekDay) {
1885
+ var self = this;
1886
+ var options = this.options;
1887
+ $weekDay.droppable({
1888
+ accept: '.wc-cal-event',
1889
+ drop: function(event, ui) {
1890
+ var $calEvent = ui.draggable;
1891
+ var top = Math.round(parseInt(ui.position.top));
1892
+ var eventDuration = self._getEventDurationFromPositionedEventElement($weekDay, $calEvent, top);
1893
+ var calEvent = $calEvent.data('calEvent');
1894
+ var newCalEvent = $.extend(true, {}, calEvent, {start: eventDuration.start, end: eventDuration.end});
1895
+ var showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length;
1896
+ if (showAsSeparatedUser) {
1897
+ // we may have dragged the event on column with a new user.
1898
+ // nice way to handle that is:
1899
+ // - get the newly dragged on user
1900
+ // - check if user is part of the event
1901
+ // - if yes, nothing changes, if not, find the old owner to remove it and add new one
1902
+ var newUserId = $weekDay.data('wcUserId');
1903
+ var userIdList = self._getEventUserId(calEvent);
1904
+ var oldUserId = $(ui.draggable.parents('.wc-day-column-inner').get(0)).data('wcUserId');
1905
+ if (!$.isArray(userIdList)) {
1906
+ userIdList = [userIdList];
1907
+ }
1908
+ if ($.inArray(newUserId, userIdList) == -1) {
1909
+ // remove old user
1910
+ var _index = $.inArray(oldUserId, userIdList);
1911
+ userIdList.splice(_index, 1);
1912
+ // add new user ?
1913
+ if ($.inArray(newUserId, userIdList) == -1) {
1914
+ userIdList.push(newUserId);
1915
+ }
1916
+ }
1917
+ newCalEvent = self._setEventUserId(newCalEvent, ((userIdList.length == 1) ? userIdList[0] : userIdList));
1918
+ }
1919
+ self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, calEvent, true);
1920
+ var $weekDayColumns = self.element.find('.wc-day-column-inner');
1921
+
1922
+ //trigger drop callback
1923
+ options.eventDrop(newCalEvent, calEvent, $calEvent);
1924
+
1925
+ var $newEvent = self._renderEvent(newCalEvent, self._findWeekDayForEvent(newCalEvent, $weekDayColumns));
1926
+ $calEvent.hide();
1927
+
1928
+ $calEvent.data('preventClick', true);
1929
+
1930
+ var $weekDayOld = self._findWeekDayForEvent($calEvent.data('calEvent'), self.element.find('.wc-time-slots .wc-day-column-inner'));
1931
+
1932
+ if ($weekDayOld.data('startDate') != $weekDay.data('startDate')) {
1933
+ self._adjustOverlappingEvents($weekDayOld);
1934
+ }
1935
+ self._adjustOverlappingEvents($weekDay);
1936
+
1937
+ setTimeout(function() {
1938
+ $calEvent.remove();
1939
+ }, 1000);
1940
+
1941
+ }
1942
+ });
1943
+ },
1944
+
1945
+ /*
1946
+ * Add resizable capabilities to a calEvent
1947
+ */
1948
+ _addResizableToCalEvent: function(calEvent, $calEvent, $weekDay) {
1949
+ var self = this;
1950
+ var options = this.options;
1951
+ $calEvent.resizable({
1952
+ grid: options.timeslotHeight,
1953
+ containment: $weekDay,
1954
+ handles: 's',
1955
+ minHeight: options.timeslotHeight,
1956
+ stop: function(event, ui) {
1957
+ var $calEvent = ui.element;
1958
+ var newEnd = new Date($calEvent.data('calEvent').start.getTime() + Math.max(1, Math.round(ui.size.height / options.timeslotHeight)) * options.millisPerTimeslot);
1959
+ if (self._needDSTdayShift($calEvent.data('calEvent').start, newEnd))
1960
+ newEnd = self._getDSTdayShift(newEnd, -1);
1961
+ var newCalEvent = $.extend(true, {}, calEvent, {start: calEvent.start, end: newEnd});
1962
+ self._adjustForEventCollisions($weekDay, $calEvent, newCalEvent, calEvent);
1963
+
1964
+ self._refreshEventDetails(newCalEvent, $calEvent);
1965
+ self._positionEvent($weekDay, $calEvent);
1966
+ self._adjustOverlappingEvents($weekDay);
1967
+ //trigger resize callback
1968
+ options.eventResize(newCalEvent, calEvent, $calEvent);
1969
+ $calEvent.data('preventClick', true);
1970
+ setTimeout(function() {
1971
+ $calEvent.removeData('preventClick');
1972
+ }, 500);
1973
+ }
1974
+ });
1975
+ $('.ui-resizable-handle', $calEvent).text('=');
1976
+ },
1977
+
1978
+ /*
1979
+ * Refresh the displayed details of a calEvent in the calendar
1980
+ */
1981
+ _refreshEventDetails: function(calEvent, $calEvent) {
1982
+ var suffix = '';
1983
+ if (!this.options.readonly &&
1984
+ this.options.allowEventDelete &&
1985
+ this.options.deletable(calEvent,$calEvent)) {
1986
+ suffix = '<div class="wc-cal-event-delete ui-icon ui-icon-close"></div>';
1987
+ }
1988
+ $calEvent.find('.wc-time').html(this.options.eventHeader(calEvent, this.element) + suffix);
1989
+ $calEvent.find('.wc-title').html(this.options.eventBody(calEvent, this.element));
1990
+ $calEvent.data('calEvent', calEvent);
1991
+ this.options.eventRefresh(calEvent, $calEvent);
1992
+ },
1993
+
1994
+ /*
1995
+ * Clear all cal events from the calendar
1996
+ */
1997
+ _clearCalendar: function() {
1998
+ this.element.find('.wc-day-column-inner div').remove();
1999
+ this._clearFreeBusys();
2000
+ },
2001
+
2002
+ /*
2003
+ * Scroll the calendar to a specific hour
2004
+ */
2005
+ _scrollToHour: function(hour, animate) {
2006
+ var self = this;
2007
+ var options = this.options;
2008
+ var $scrollable = this.element.find('.wc-scrollable-grid');
2009
+ var slot = hour;
2010
+ if (self.options.businessHours.limitDisplay) {
2011
+ if (hour <= self.options.businessHours.start) {
2012
+ slot = 0;
2013
+ } else if (hour >= self.options.businessHours.end) {
2014
+ slot = self.options.businessHours.end - self.options.businessHours.start - 1;
2015
+ } else {
2016
+ slot = hour - self.options.businessHours.start;
2017
+ }
2018
+ }
2019
+
2020
+ var $target = this.element.find('.wc-grid-timeslot-header .wc-hour-header:eq(' + slot + ')');
2021
+
2022
+ $scrollable.animate({scrollTop: 0}, 0, function() {
2023
+ var targetOffset = $target.offset().top;
2024
+ var scroll = targetOffset - $scrollable.offset().top - $target.outerHeight();
2025
+ if (animate) {
2026
+ $scrollable.animate({scrollTop: scroll}, options.scrollToHourMillis);
2027
+ }
2028
+ else {
2029
+ $scrollable.animate({scrollTop: scroll}, 0);
2030
+ }
2031
+ });
2032
+ },
2033
+
2034
+ /*
2035
+ * find the hour (12 hour day) for a given hour index
2036
+ */
2037
+ _hourForIndex: function(index) {
2038
+ if (index === 0) { //midnight
2039
+ return 12;
2040
+ } else if (index < 13) { //am
2041
+ return index;
2042
+ } else { //pm
2043
+ return index - 12;
2044
+ }
2045
+ },
2046
+
2047
+ _24HourForIndex: function(index) {
2048
+ if (index === 0) { //midnight
2049
+ return '00:00';
2050
+ } else if (index < 10) {
2051
+ return '0' + index + ':00';
2052
+ } else {
2053
+ return index + ':00';
2054
+ }
2055
+ },
2056
+
2057
+ _amOrPm: function(hourOfDay) {
2058
+ return hourOfDay < 12 ? 'AM' : 'PM';
2059
+ },
2060
+
2061
+ _isToday: function(date) {
2062
+ var clonedDate = this._cloneDate(date);
2063
+ this._clearTime(clonedDate);
2064
+ var today = new Date();
2065
+ this._clearTime(today);
2066
+ return today.getTime() === clonedDate.getTime();
2067
+ },
2068
+
2069
+ /*
2070
+ * Clean events to ensure correct format
2071
+ */
2072
+ _cleanEvents: function(events) {
2073
+ var self = this;
2074
+ $.each(events, function(i, event) {
2075
+ self._cleanEvent(event);
2076
+ });
2077
+ return events;
2078
+ },
2079
+
2080
+ /*
2081
+ * Clean specific event
2082
+ */
2083
+ _cleanEvent: function(event) {
2084
+ if (event.date) {
2085
+ event.start = event.date;
2086
+ }
2087
+ event.start = this._cleanDate(event.start);
2088
+ event.end = this._cleanDate(event.end);
2089
+ if (!event.end) {
2090
+ event.end = this._addDays(this._cloneDate(event.start), 1);
2091
+ }
2092
+ },
2093
+
2094
+ /*
2095
+ * Disable text selection of the elements in different browsers
2096
+ */
2097
+ _disableTextSelect: function($elements) {
2098
+ $elements.each(function() {
2099
+ if ($.browser.mozilla) {//Firefox
2100
+ $(this).css('MozUserSelect', 'none');
2101
+ } else if ($.browser.msie) {//IE
2102
+ $(this).bind('selectstart', function() {
2103
+ return false;
2104
+ });
2105
+ } else {//Opera, etc.
2106
+ $(this).mousedown(function() {
2107
+ return false;
2108
+ });
2109
+ }
2110
+ });
2111
+ },
2112
+
2113
+ /*
2114
+ * returns the date on the first millisecond of the week
2115
+ */
2116
+ _dateFirstDayOfWeek: function(date) {
2117
+ var self = this;
2118
+ var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
2119
+ var adjustedDate = new Date(midnightCurrentDate);
2120
+ adjustedDate.setDate(adjustedDate.getDate() - self._getAdjustedDayIndex(midnightCurrentDate));
2121
+
2122
+ return adjustedDate;
2123
+ },
2124
+
2125
+ /*
2126
+ * returns the date on the first millisecond of the last day of the week
2127
+ */
2128
+ _dateLastDayOfWeek: function(date) {
2129
+ var self = this;
2130
+ var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
2131
+ var adjustedDate = new Date(midnightCurrentDate);
2132
+ var daysToAdd = (self.options.daysToShow - 1 - self._getAdjustedDayIndex(midnightCurrentDate));
2133
+ adjustedDate.setDate(adjustedDate.getDate() + daysToAdd);
2134
+
2135
+ return adjustedDate;
2136
+ },
2137
+
2138
+ /**
2139
+ * fix the date if it is not within given options
2140
+ * minDate and maxDate
2141
+ */
2142
+ _fixMinMaxDate: function(date) {
2143
+ var minDate, maxDate;
2144
+ date = this._cleanDate(date);
2145
+
2146
+ // not less than minDate
2147
+ if (this.options.minDate) {
2148
+ minDate = this._cleanDate(this.options.minDate);
2149
+ // midnight on minDate
2150
+ minDate = new Date(minDate.getFullYear(), minDate.getMonth(), minDate.getDate());
2151
+ if (date.getTime() < minDate.getTime()) {
2152
+ this._trigger('reachedmindate', this.element, date);
2153
+ }
2154
+ date = this._cleanDate(Math.max(date.getTime(), minDate.getTime()));
2155
+ }
2156
+
2157
+ // not more than maxDate
2158
+ if (this.options.maxDate) {
2159
+ maxDate = this._cleanDate(this.options.maxDate);
2160
+ // apply correction for max date if not startOnFirstDayOfWeek
2161
+ // to make sure no further date is displayed.
2162
+ // otherwise, the complement will still be shown
2163
+ if (!this._startOnFirstDayOfWeek()) {
2164
+ var day = maxDate.getDate() - this.options.daysToShow + 1;
2165
+ maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), day);
2166
+ }
2167
+ // microsecond before midnight on maxDate
2168
+ maxDate = new Date(maxDate.getFullYear(), maxDate.getMonth(), maxDate.getDate(), 23, 59, 59, 999);
2169
+ if (date.getTime() > maxDate.getTime()) {
2170
+ this._trigger('reachedmaxdate', this.element, date);
2171
+ }
2172
+ date = this._cleanDate(Math.min(date.getTime(), maxDate.getTime()));
2173
+ }
2174
+
2175
+ return date;
2176
+ },
2177
+
2178
+ /*
2179
+ * gets the index of the current day adjusted based on options
2180
+ */
2181
+ _getAdjustedDayIndex: function(date) {
2182
+ if (!this._startOnFirstDayOfWeek()) {
2183
+ return 0;
2184
+ }
2185
+
2186
+ var midnightCurrentDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
2187
+ var currentDayOfStandardWeek = midnightCurrentDate.getDay();
2188
+ var days = [0, 1, 2, 3, 4, 5, 6];
2189
+ this._rotate(days, this._firstDayOfWeek());
2190
+ return days[currentDayOfStandardWeek];
2191
+ },
2192
+
2193
+ _firstDayOfWeek: function() {
2194
+ if ($.isFunction(this.options.firstDayOfWeek)) {
2195
+ return this.options.firstDayOfWeek(this.element);
2196
+ }
2197
+ return this.options.firstDayOfWeek;
2198
+ },
2199
+
2200
+ /*
2201
+ * returns the date on the last millisecond of the week
2202
+ */
2203
+ _dateLastMilliOfWeek: function(date) {
2204
+ var lastDayOfWeek = this._dateLastDayOfWeek(date);
2205
+ lastDayOfWeek = this._cloneDate(lastDayOfWeek);
2206
+ lastDayOfWeek.setDate(lastDayOfWeek.getDate() + 1);
2207
+ return lastDayOfWeek;
2208
+
2209
+ },
2210
+
2211
+ /*
2212
+ * Clear the time components of a date leaving the date
2213
+ * of the first milli of day
2214
+ */
2215
+ _clearTime: function(d) {
2216
+ d.setHours(0);
2217
+ d.setMinutes(0);
2218
+ d.setSeconds(0);
2219
+ d.setMilliseconds(0);
2220
+ return d;
2221
+ },
2222
+
2223
+ /*
2224
+ * add specific number of days to date
2225
+ */
2226
+ _addDays: function(d, n, keepTime) {
2227
+ d.setDate(d.getDate() + n);
2228
+ if (keepTime) {
2229
+ return d;
2230
+ }
2231
+ return this._clearTime(d);
2232
+ },
2233
+
2234
+ /*
2235
+ * Rotate an array by specified number of places.
2236
+ */
2237
+ _rotate: function(a /*array*/, p /* integer, positive integer rotate to the right, negative to the left... */) {
2238
+ for (var l = a.length, p = (Math.abs(p) >= l && (p %= l), p < 0 && (p += l), p), i, x; p; p = (Math.ceil(l / p) - 1) * p - l + (l = p)) {
2239
+ for (i = l; i > p; x = a[--i], a[i] = a[i - p], a[i - p] = x) {}
2240
+ }
2241
+ return a;
2242
+ },
2243
+
2244
+ _cloneDate: function(d) {
2245
+ return new Date(d.getTime());
2246
+ },
2247
+
2248
+ /*
2249
+ * return a date for different representations
2250
+ */
2251
+ _cleanDate: function(d) {
2252
+ if (typeof d == 'string') {
2253
+ // if is numeric
2254
+ if (!isNaN(parseFloat(d)) && isFinite()) {
2255
+ return this._cleanDate(parseInt(d, 10));
2256
+ }
2257
+ // this is a human readable date
2258
+ return Date.parse(d) || new Date(d);
2259
+ }
2260
+ if (typeof d == 'number') {
2261
+ return new Date(d);
2262
+ }
2263
+ return d;
2264
+ },
2265
+
2266
+ /*
2267
+ * date formatting is adapted from
2268
+ * http://jacwright.com/projects/javascript/date_format
2269
+ */
2270
+ _formatDate: function(date, format) {
2271
+ var returnStr = '';
2272
+ for (var i = 0; i < format.length; i++) {
2273
+ var curChar = format.charAt(i);
2274
+ if (i != 0 && format.charAt(i - 1) == '\\') {
2275
+ returnStr += curChar;
2276
+ }
2277
+ else if (this._replaceChars[curChar]) {
2278
+ returnStr += this._replaceChars[curChar](date, this);
2279
+ } else if (curChar != '\\') {
2280
+ returnStr += curChar;
2281
+ }
2282
+ }
2283
+ return returnStr;
2284
+ },
2285
+
2286
+ _replaceChars: {
2287
+ // Day
2288
+ d: function(date) { return (date.getDate() < 10 ? '0' : '') + date.getDate(); },
2289
+ D: function(date, calendar) { return calendar.options.shortDays[date.getDay()]; },
2290
+ j: function(date) { return date.getDate(); },
2291
+ l: function(date, calendar) { return calendar.options.longDays[date.getDay()]; },
2292
+ N: function(date) { var _d = date.getDay(); return _d ? _d : 7; },
2293
+ S: function(date) { return (date.getDate() % 10 == 1 && date.getDate() != 11 ? 'st' : (date.getDate() % 10 == 2 && date.getDate() != 12 ? 'nd' : (date.getDate() % 10 == 3 && date.getDate() != 13 ? 'rd' : 'th'))); },
2294
+ w: function(date) { return date.getDay(); },
2295
+ z: function(date) { var d = new Date(date.getFullYear(), 0, 1); return Math.ceil((date - d) / 86400000); }, // Fixed now
2296
+ // Week
2297
+ W: function(date) { var d = new Date(date.getFullYear(), 0, 1); return Math.ceil((((date - d) / 86400000) + d.getDay() + 1) / 7); }, // Fixed now
2298
+ // Month
2299
+ F: function(date, calendar) { return calendar.options.longMonths[date.getMonth()]; },
2300
+ m: function(date) { return (date.getMonth() < 9 ? '0' : '') + (date.getMonth() + 1); },
2301
+ M: function(date, calendar) { return calendar.options.shortMonths[date.getMonth()]; },
2302
+ n: function(date) { return date.getMonth() + 1; },
2303
+ t: function(date) { var d = date; return new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate() }, // Fixed now, gets #days of date
2304
+ // Year
2305
+ L: function(date) { var year = date.getFullYear(); return (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)); }, // Fixed now
2306
+ o: function(date) { var d = new Date(date.valueOf()); d.setDate(d.getDate() - ((date.getDay() + 6) % 7) + 3); return d.getFullYear();}, //Fixed now
2307
+ Y: function(date) { return date.getFullYear(); },
2308
+ y: function(date) { return ('' + date.getFullYear()).substr(2); },
2309
+ // Time
2310
+ a: function(date) { return date.getHours() < 12 ? 'am' : 'pm'; },
2311
+ A: function(date) { return date.getHours() < 12 ? 'AM' : 'PM'; },
2312
+ B: function(date) { return Math.floor((((date.getUTCHours() + 1) % 24) + date.getUTCMinutes() / 60 + date.getUTCSeconds() / 3600) * 1000 / 24); }, // Fixed now
2313
+ g: function(date) { return date.getHours() % 12 || 12; },
2314
+ G: function(date) { return date.getHours(); },
2315
+ h: function(date) { return ((date.getHours() % 12 || 12) < 10 ? '0' : '') + (date.getHours() % 12 || 12); },
2316
+ H: function(date) { return (date.getHours() < 10 ? '0' : '') + date.getHours(); },
2317
+ i: function(date) { return (date.getMinutes() < 10 ? '0' : '') + date.getMinutes(); },
2318
+ s: function(date) { return (date.getSeconds() < 10 ? '0' : '') + date.getSeconds(); },
2319
+ u: function(date) { var m = date.getMilliseconds(); return (m < 10 ? '00' : (m < 100 ? '0' : '')) + m; },
2320
+ // Timezone
2321
+ e: function(date) { return 'Not Yet Supported'; },
2322
+ I: function(date) { return 'Not Yet Supported'; },
2323
+ O: function(date) { return (-date.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(date.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(date.getTimezoneOffset() / 60)) + '00'; },
2324
+ P: function(date) { return (-date.getTimezoneOffset() < 0 ? '-' : '+') + (Math.abs(date.getTimezoneOffset() / 60) < 10 ? '0' : '') + (Math.abs(date.getTimezoneOffset() / 60)) + ':00'; }, // Fixed now
2325
+ T: function(date) { var m = date.getMonth(); date.setMonth(0); var result = date.toTimeString().replace(/^.+ \(?([^\)]+)\)?$/, '$1'); date.setMonth(m); return result;},
2326
+ Z: function(date) { return -date.getTimezoneOffset() * 60; },
2327
+ // Full Date/Time
2328
+ c: function(date, calendar) { return calendar._formatDate(date, 'Y-m-d\\TH:i:sP'); }, // Fixed now
2329
+ r: function(date, calendar) { return calendar._formatDate(date, 'D, d M Y H:i:s O'); },
2330
+ U: function(date) { return date.getTime() / 1000; }
2331
+ },
2332
+
2333
+ /* USER MANAGEMENT FUNCTIONS */
2334
+
2335
+ getUserForId: function(id) {
2336
+ return $.extend({}, this.options.users[this._getUserIndexFromId(id)]);
2337
+ },
2338
+
2339
+ /**
2340
+ * return the user name for header
2341
+ */
2342
+ _getUserName: function(index) {
2343
+ var self = this;
2344
+ var options = this.options;
2345
+ var user = options.users[index];
2346
+ if ($.isFunction(options.getUserName)) {
2347
+ return options.getUserName(user, index, self.element);
2348
+ }
2349
+ else {
2350
+ return user;
2351
+ }
2352
+ },
2353
+ /**
2354
+ * return the user id for given index
2355
+ */
2356
+ _getUserIdFromIndex: function(index) {
2357
+ var self = this;
2358
+ var options = this.options;
2359
+ if ($.isFunction(options.getUserId)) {
2360
+ return options.getUserId(options.users[index], index, self.element);
2361
+ }
2362
+ return index;
2363
+ },
2364
+ /**
2365
+ * returns the associated user index for given ID
2366
+ */
2367
+ _getUserIndexFromId: function(id) {
2368
+ var self = this;
2369
+ var options = this.options;
2370
+ for (var i = 0; i < options.users.length; i++) {
2371
+ if (self._getUserIdFromIndex(i) == id) {
2372
+ return i;
2373
+ }
2374
+ }
2375
+ return 0;
2376
+ },
2377
+ /**
2378
+ * return the user ids for given calEvent.
2379
+ * default is calEvent.userId field.
2380
+ */
2381
+ _getEventUserId: function(calEvent) {
2382
+ var self = this;
2383
+ var options = this.options;
2384
+ if (options.showAsSeparateUsers && options.users && options.users.length) {
2385
+ if ($.isFunction(options.getEventUserId)) {
2386
+ return options.getEventUserId(calEvent, self.element);
2387
+ }
2388
+ return calEvent.userId;
2389
+ }
2390
+ return [];
2391
+ },
2392
+ /**
2393
+ * sets the event user id on given calEvent
2394
+ * default is calEvent.userId field.
2395
+ */
2396
+ _setEventUserId: function(calEvent, userId) {
2397
+ var self = this;
2398
+ var options = this.options;
2399
+ if ($.isFunction(options.setEventUserId)) {
2400
+ return options.setEventUserId(userId, calEvent, self.element);
2401
+ }
2402
+ calEvent.userId = userId;
2403
+ return calEvent;
2404
+ },
2405
+ /**
2406
+ * return the user ids for given freeBusy.
2407
+ * default is freeBusy.userId field.
2408
+ */
2409
+ _getFreeBusyUserId: function(freeBusy) {
2410
+ var self = this;
2411
+ var options = this.options;
2412
+ if ($.isFunction(options.getFreeBusyUserId)) {
2413
+ return options.getFreeBusyUserId(freeBusy.getOption(), self.element);
2414
+ }
2415
+ return freeBusy.getOption('userId');
2416
+ },
2417
+
2418
+ /* FREEBUSY MANAGEMENT */
2419
+
2420
+ /**
2421
+ * ckean the free busy managers and remove all the freeBusy
2422
+ */
2423
+ _clearFreeBusys: function() {
2424
+ if (this.options.displayFreeBusys) {
2425
+ var self = this,
2426
+ options = this.options,
2427
+ $freeBusyPlaceholders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy');
2428
+ $freeBusyPlaceholders.each(function() {
2429
+ $(this).data('wcFreeBusyManager', new FreeBusyManager({
2430
+ start: self._cloneDate($(this).data('startDate')),
2431
+ end: self._cloneDate($(this).data('endDate')),
2432
+ defaultFreeBusy: options.defaultFreeBusy || {}
2433
+ }));
2434
+ });
2435
+ self.element.find('.wc-grid-row-freebusy .wc-freebusy').remove();
2436
+ }
2437
+ },
2438
+ /**
2439
+ * retrieve placeholders for given freebusy
2440
+ */
2441
+ _findWeekDaysForFreeBusy: function(freeBusy, $weekDays) {
2442
+ var $returnWeekDays,
2443
+ options = this.options,
2444
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
2445
+ self = this,
2446
+ userList = self._getFreeBusyUserId(freeBusy);
2447
+ if (!$.isArray(userList)) {
2448
+ userList = userList != 'undefined' ? [userList] : [];
2449
+ }
2450
+ if (!$weekDays) {
2451
+ $weekDays = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy');
2452
+ }
2453
+ $weekDays.each(function() {
2454
+ var manager = $(this).data('wcFreeBusyManager'),
2455
+ has_overlap = manager.isWithin(freeBusy.getStart()) ||
2456
+ manager.isWithin(freeBusy.getEnd()) ||
2457
+ freeBusy.isWithin(manager.getStart()) ||
2458
+ freeBusy.isWithin(manager.getEnd()),
2459
+ userId = $(this).data('wcUserId');
2460
+ if (has_overlap && (!showAsSeparatedUser || ($.inArray(userId, userList) != -1))) {
2461
+ $returnWeekDays = $returnWeekDays ? $returnWeekDays.add($(this)) : $(this);
2462
+ }
2463
+ });
2464
+ return $returnWeekDays;
2465
+ },
2466
+
2467
+ /**
2468
+ * used to render all freeBusys
2469
+ */
2470
+ _renderFreeBusys: function(freeBusys) {
2471
+ if (this.options.displayFreeBusys) {
2472
+ var self = this,
2473
+ $freeBusyPlaceholders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
2474
+ freebusysToRender;
2475
+ //insert freebusys to dedicated placeholders freebusy managers
2476
+ if ($.isArray(freeBusys)) {
2477
+ freebusysToRender = self._cleanFreeBusys(freeBusys);
2478
+ } else if (freeBusys.freebusys) {
2479
+ freebusysToRender = self._cleanFreeBusys(freeBusys.freebusys);
2480
+ }
2481
+ else {
2482
+ freebusysToRender = [];
2483
+ }
2484
+
2485
+ $.each(freebusysToRender, function(index, freebusy) {
2486
+ var $placeholders = self._findWeekDaysForFreeBusy(freebusy, $freeBusyPlaceholders);
2487
+ if ($placeholders) {
2488
+ $placeholders.each(function() {
2489
+ var manager = $(this).data('wcFreeBusyManager');
2490
+ manager.insertFreeBusy(new FreeBusy(freebusy.getOption()));
2491
+ $(this).data('wcFreeBusyManager', manager);
2492
+ });
2493
+ }
2494
+ });
2495
+
2496
+ //now display freebusys on place holders
2497
+ self._refreshFreeBusys($freeBusyPlaceholders);
2498
+ }
2499
+ },
2500
+ /**
2501
+ * refresh freebusys for given placeholders
2502
+ */
2503
+ _refreshFreeBusys: function($freeBusyPlaceholders) {
2504
+ if (this.options.displayFreeBusys && $freeBusyPlaceholders) {
2505
+ var self = this,
2506
+ options = this.options,
2507
+ start = (options.businessHours.limitDisplay ? options.businessHours.start : 0),
2508
+ end = (options.businessHours.limitDisplay ? options.businessHours.end : 24);
2509
+
2510
+ $freeBusyPlaceholders.each(function() {
2511
+ var $placehoder = $(this);
2512
+ var s = self._cloneDate($placehoder.data('startDate')),
2513
+ e = self._cloneDate(s);
2514
+ s.setHours(start);
2515
+ e.setHours(end);
2516
+ $placehoder.find('.wc-freebusy').remove();
2517
+ $.each($placehoder.data('wcFreeBusyManager').getFreeBusys(s, e), function() {
2518
+ self._renderFreeBusy(this, $placehoder);
2519
+ });
2520
+ });
2521
+ }
2522
+ },
2523
+ /**
2524
+ * render a freebusy item on dedicated placeholders
2525
+ */
2526
+ _renderFreeBusy: function(freeBusy, $freeBusyPlaceholder) {
2527
+ if (this.options.displayFreeBusys) {
2528
+ var self = this,
2529
+ options = this.options,
2530
+ freeBusyHtml = '<div class="wc-freebusy"></div>';
2531
+
2532
+ var $fb = $(freeBusyHtml);
2533
+ $fb.data('wcFreeBusy', new FreeBusy(freeBusy.getOption()));
2534
+ this._positionFreeBusy($freeBusyPlaceholder, $fb);
2535
+ $fb = options.freeBusyRender(freeBusy.getOption(), $fb, self.element);
2536
+ if ($fb) {
2537
+ $fb.appendTo($freeBusyPlaceholder);
2538
+ }
2539
+ }
2540
+ },
2541
+ /*
2542
+ * Position the freebusy element within the weekday based on it's start / end dates.
2543
+ */
2544
+ _positionFreeBusy: function($placeholder, $freeBusy) {
2545
+ var options = this.options;
2546
+ var freeBusy = $freeBusy.data('wcFreeBusy');
2547
+ var pxPerMillis = $placeholder.height() / options.millisToDisplay;
2548
+ var firstHourDisplayed = options.businessHours.limitDisplay ? options.businessHours.start : 0;
2549
+ var startMillis = freeBusy.getStart().getTime() - new Date(freeBusy.getStart().getFullYear(), freeBusy.getStart().getMonth(), freeBusy.getStart().getDate(), firstHourDisplayed).getTime();
2550
+ var eventMillis = freeBusy.getEnd().getTime() - freeBusy.getStart().getTime();
2551
+ var pxTop = pxPerMillis * startMillis;
2552
+ var pxHeight = pxPerMillis * eventMillis;
2553
+ $freeBusy.css({top: pxTop, height: pxHeight});
2554
+ },
2555
+ /*
2556
+ * Clean freebusys to ensure correct format
2557
+ */
2558
+ _cleanFreeBusys: function(freebusys) {
2559
+ var self = this,
2560
+ freeBusyToReturn = [];
2561
+ if (!$.isArray(freebusys)) {
2562
+ var freebusys = [freebusys];
2563
+ }
2564
+ $.each(freebusys, function(i, freebusy) {
2565
+ freeBusyToReturn.push(new FreeBusy(self._cleanFreeBusy(freebusy)));
2566
+ });
2567
+ return freeBusyToReturn;
2568
+ },
2569
+
2570
+ /*
2571
+ * Clean specific freebusy
2572
+ */
2573
+ _cleanFreeBusy: function(freebusy) {
2574
+ if (freebusy.date) {
2575
+ freebusy.start = freebusy.date;
2576
+ }
2577
+ freebusy.start = this._cleanDate(freebusy.start);
2578
+ freebusy.end = this._cleanDate(freebusy.end);
2579
+ return freebusy;
2580
+ },
2581
+
2582
+ /**
2583
+ * retrives the first freebusy manager matching demand.
2584
+ */
2585
+ getFreeBusyManagersFor: function(date, users) {
2586
+ var calEvent = {
2587
+ start: date,
2588
+ end: date
2589
+ };
2590
+ this._setEventUserId(calEvent, users);
2591
+ return this.getFreeBusyManagerForEvent(calEvent);
2592
+ },
2593
+ /**
2594
+ * retrives the first freebusy manager for given event.
2595
+ */
2596
+ getFreeBusyManagerForEvent: function(newCalEvent) {
2597
+ var self = this,
2598
+ options = this.options,
2599
+ freeBusyManager;
2600
+ if (options.displayFreeBusys) {
2601
+ var $freeBusyPlaceHoders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
2602
+ freeBusy = new FreeBusy({start: newCalEvent.start, end: newCalEvent.end}),
2603
+ showAsSeparatedUser = options.showAsSeparateUsers && options.users && options.users.length,
2604
+ userId = showAsSeparatedUser ? self._getEventUserId(newCalEvent) : null;
2605
+ if (!$.isArray(userId)) {
2606
+ userId = [userId];
2607
+ }
2608
+ $freeBusyPlaceHoders.each(function() {
2609
+ var manager = $(this).data('wcFreeBusyManager'),
2610
+ has_overlap = manager.isWithin(freeBusy.getEnd()) ||
2611
+ manager.isWithin(freeBusy.getEnd()) ||
2612
+ freeBusy.isWithin(manager.getStart()) ||
2613
+ freeBusy.isWithin(manager.getEnd());
2614
+ if (has_overlap && (!showAsSeparatedUser || $.inArray($(this).data('wcUserId'), userId) != -1)) {
2615
+ freeBusyManager = $(this).data('wcFreeBusyManager');
2616
+ return false;
2617
+ }
2618
+ });
2619
+ }
2620
+ return freeBusyManager;
2621
+ },
2622
+ /**
2623
+ * appends the freebusys to replace the old ones.
2624
+ * @param {array|object} freeBusys freebusy(s) to apply.
2625
+ */
2626
+ updateFreeBusy: function(freeBusys) {
2627
+ var self = this,
2628
+ options = this.options;
2629
+ if (options.displayFreeBusys) {
2630
+ var $toRender,
2631
+ $freeBusyPlaceHoders = self.element.find('.wc-grid-row-freebusy .wc-column-freebusy'),
2632
+ _freeBusys = self._cleanFreeBusys(freeBusys);
2633
+
2634
+ $.each(_freeBusys, function(index, _freeBusy) {
2635
+
2636
+ var $weekdays = self._findWeekDaysForFreeBusy(_freeBusy, $freeBusyPlaceHoders);
2637
+ //if freebusy has a placeholder
2638
+ if ($weekdays && $weekdays.length) {
2639
+ $weekdays.each(function(index, day) {
2640
+ var manager = $(day).data('wcFreeBusyManager');
2641
+ manager.insertFreeBusy(_freeBusy);
2642
+ $(day).data('wcFreeBusyManager', manager);
2643
+ });
2644
+ $toRender = $toRender ? $toRender.add($weekdays) : $weekdays;
2645
+ }
2646
+ });
2647
+ self._refreshFreeBusys($toRender);
2648
+ }
2649
+ },
2650
+
2651
+ /* NEW OPTIONS MANAGEMENT */
2652
+
2653
+ /**
2654
+ * checks wether or not the calendar should be displayed starting on first day of week
2655
+ */
2656
+ _startOnFirstDayOfWeek: function() {
2657
+ return jQuery.isFunction(this.options.startOnFirstDayOfWeek) ? this.options.startOnFirstDayOfWeek(this.element) : this.options.startOnFirstDayOfWeek;
2658
+ },
2659
+
2660
+ /**
2661
+ * finds out the current scroll to apply it when changing the view
2662
+ */
2663
+ _getCurrentScrollHour: function() {
2664
+ var self = this;
2665
+ var options = this.options;
2666
+ var $scrollable = this.element.find('.wc-scrollable-grid');
2667
+ var scroll = $scrollable.scrollTop();
2668
+ if (self.options.businessHours.limitDisplay) {
2669
+ scroll = scroll + options.businessHours.start * options.timeslotHeight * options.timeslotsPerHour;
2670
+ }
2671
+ return Math.round(scroll / (options.timeslotHeight * options.timeslotsPerHour)) + 1;
2672
+ },
2673
+ _getJsonOptions: function() {
2674
+ if ($.isFunction(this.options.jsonOptions)) {
2675
+ return $.extend({}, this.options.jsonOptions(this.element));
2676
+ }
2677
+ if ($.isPlainObject(this.options.jsonOptions)) {
2678
+ return $.extend({}, this.options.jsonOptions);
2679
+ }
2680
+ return {};
2681
+ },
2682
+ _getHeaderDate: function(date) {
2683
+ var options = this.options;
2684
+ if (options.getHeaderDate && $.isFunction(options.getHeaderDate))
2685
+ {
2686
+ return options.getHeaderDate(date, this.element);
2687
+ }
2688
+ var dayName = options.useShortDayNames ? options.shortDays[date.getDay()] : options.longDays[date.getDay()];
2689
+ return dayName + (options.headerSeparator) + this._formatDate(date, options.dateFormat);
2690
+ },
2691
+
2692
+
2693
+
2694
+ /**
2695
+ * returns corrected date related to DST problem
2696
+ */
2697
+ _getDSTdayShift: function(date, shift) {
2698
+ var start = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0);
2699
+ var offset1 = start.getTimezoneOffset();
2700
+ var offset2 = date.getTimezoneOffset();
2701
+ if (offset1 == offset2)
2702
+ return date;
2703
+ shift = shift ? shift : 1;
2704
+ return new Date(date.getTime() - shift * (offset1 > offset2 ? -1 : 1) * (Math.max(offset1, offset2) - Math.min(offset1, offset2)) * 60000);
2705
+ },
2706
+ _needDSTdayShift: function(date1, date2) {
2707
+ return date1.getTimezoneOffset() != date2.getTimezoneOffset();
2708
+ }
2709
+
2710
+
2711
+
2712
+ }; // end of widget function return
2713
+ })() //end of widget function closure execution
2714
+ ); // end of $.widget("ui.weekCalendar"...
2715
+
2716
+ $.extend($.ui.weekCalendar, {
2717
+ version: '2.0-dev',
2718
+ updateLayoutOptions: {
2719
+ startOnFirstDayOfWeek: true,
2720
+ firstDayOfWeek: true,
2721
+ daysToShow: true,
2722
+ displayOddEven: true,
2723
+ timeFormat: true,
2724
+ dateFormat: true,
2725
+ use24Hour: true,
2726
+ useShortDayNames: true,
2727
+ businessHours: true,
2728
+ timeslotHeight: true,
2729
+ timeslotsPerHour: true,
2730
+ buttonText: true,
2731
+ height: true,
2732
+ shortMonths: true,
2733
+ longMonths: true,
2734
+ shortDays: true,
2735
+ longDays: true,
2736
+ textSize: true,
2737
+ users: true,
2738
+ showAsSeparateUsers: true,
2739
+ displayFreeBusys: true
2740
+ }
2741
+ });
2742
+
2743
+ var MILLIS_IN_DAY = 86400000;
2744
+ var MILLIS_IN_WEEK = MILLIS_IN_DAY * 7;
2745
+
2746
+ /* FREE BUSY MANAGERS */
2747
+ var FreeBusyProto = {
2748
+ getStart: function() {return this.getOption('start')},
2749
+ getEnd: function() {return this.getOption('end')},
2750
+ getOption: function() {
2751
+ if (!arguments.length) { return this.options }
2752
+ if (typeof(this.options[arguments[0]]) !== 'undefined') {
2753
+ return this.options[arguments[0]];
2754
+ }
2755
+ else if (typeof(arguments[1]) !== 'undefined') {
2756
+ return arguments[1];
2757
+ }
2758
+ return null;
2759
+ },
2760
+ setOption: function(key, value) {
2761
+ if (arguments.length == 1) {
2762
+ $.extend(this.options, arguments[0]);
2763
+ return this;
2764
+ }
2765
+ this.options[key] = value;
2766
+ return this;
2767
+ },
2768
+ isWithin: function(dateTime) {return Math.floor(dateTime.getTime() / 1000) >= Math.floor(this.getStart().getTime() / 1000) && Math.floor(dateTime.getTime() / 1000) <= Math.floor(this.getEnd().getTime() / 1000)},
2769
+ isValid: function() {return this.getStart().getTime() < this.getEnd().getTime()}
2770
+ };
2771
+
2772
+ /**
2773
+ * @constructor
2774
+ * single user freebusy manager.
2775
+ */
2776
+ var FreeBusy = function(options) {
2777
+ this.options = $.extend({}, options || {});
2778
+ };
2779
+ $.extend(FreeBusy.prototype, FreeBusyProto);
2780
+
2781
+ var FreeBusyManager = function(options) {
2782
+ this.options = $.extend({
2783
+ defaultFreeBusy: {}
2784
+ }, options || {});
2785
+ this.freeBusys = [];
2786
+ this.freeBusys.push(new FreeBusy($.extend({
2787
+ start: this.getStart(),
2788
+ end: this.getEnd()
2789
+ }, this.options.defaultFreeBusy)));
2790
+ };
2791
+ $.extend(FreeBusyManager.prototype, FreeBusyProto, {
2792
+ /**
2793
+ * return matching freeBusys.
2794
+ * if you do not pass any argument, returns all freebusys.
2795
+ * if you only pass a start date, only matchinf freebusy will be returned.
2796
+ * if you pass 2 arguments, then all freebusys available within the time period will be returned
2797
+ * @param {Date} start [optionnal] if you do not pass end date, will return the freeBusy within which this date falls.
2798
+ * @param {Date} end [optionnal] the date where to stop the search.
2799
+ * @return {Array} an array of FreeBusy matching arguments.
2800
+ */
2801
+ getFreeBusys: function() {
2802
+ switch (arguments.length) {
2803
+ case 0:
2804
+ return this.freeBusys;
2805
+ case 1:
2806
+ var freeBusy = [];
2807
+ var start = arguments[0];
2808
+ if (!this.isWithin(start)) {
2809
+ return freeBusy;
2810
+ }
2811
+ $.each(this.freeBusys, function() {
2812
+ if (this.isWithin(start)) {
2813
+ freeBusy.push(this);
2814
+ }
2815
+ if (Math.floor(this.getEnd().getTime() / 1000) > Math.floor(start.getTime() / 1000)) {
2816
+ return false;
2817
+ }
2818
+ });
2819
+ return freeBusy;
2820
+ default:
2821
+ //we assume only 2 first args are revealants
2822
+ var freeBusy = [];
2823
+ var start = arguments[0], end = arguments[1];
2824
+ var tmpFreeBusy = new FreeBusy({start: start, end: end});
2825
+ if (end.getTime() < start.getTime() || this.getStart().getTime() > end.getTime() || this.getEnd().getTime() < start.getTime()) {
2826
+ return freeBusy;
2827
+ }
2828
+ $.each(this.freeBusys, function() {
2829
+ if (this.getStart().getTime() >= end.getTime()) {
2830
+ return false;
2831
+ }
2832
+ if (tmpFreeBusy.isWithin(this.getStart()) && tmpFreeBusy.isWithin(this.getEnd())) {
2833
+ freeBusy.push(this);
2834
+ }
2835
+ else if (this.isWithin(tmpFreeBusy.getStart()) && this.isWithin(tmpFreeBusy.getEnd())) {
2836
+ var _f = new FreeBusy(this.getOption());
2837
+ _f.setOption('end', tmpFreeBusy.getEnd());
2838
+ _f.setOption('start', tmpFreeBusy.getStart());
2839
+ freeBusy.push(_f);
2840
+ }
2841
+ else if (this.isWithin(tmpFreeBusy.getStart()) && this.getStart().getTime() < start.getTime()) {
2842
+ var _f = new FreeBusy(this.getOption());
2843
+ _f.setOption('start', tmpFreeBusy.getStart());
2844
+ freeBusy.push(_f);
2845
+ }
2846
+ else if (this.isWithin(tmpFreeBusy.getEnd()) && this.getEnd().getTime() > end.getTime()) {
2847
+ var _f = new FreeBusy(this.getOption());
2848
+ _f.setOption('end', tmpFreeBusy.getEnd());
2849
+ freeBusy.push(_f);
2850
+ }
2851
+ });
2852
+ return freeBusy;
2853
+ }
2854
+ },
2855
+ insertFreeBusy: function(freeBusy) {
2856
+ var freeBusy = new FreeBusy(freeBusy.getOption());
2857
+ //first, if inserted freebusy is bigger than manager
2858
+ if (freeBusy.getStart().getTime() < this.getStart().getTime()) {
2859
+ freeBusy.setOption('start', this.getStart());
2860
+ }
2861
+ if (freeBusy.getEnd().getTime() > this.getEnd().getTime()) {
2862
+ freeBusy.setOption('end', this.getEnd());
2863
+ }
2864
+ var start = freeBusy.getStart(), end = freeBusy.getEnd(),
2865
+ startIndex = 0, endIndex = this.freeBusys.length - 1,
2866
+ newFreeBusys = [];
2867
+ var pushNewFreeBusy = function(_f) {if (_f.isValid()) newFreeBusys.push(_f);};
2868
+
2869
+ $.each(this.freeBusys, function(index) {
2870
+ //within the loop, we have following vars:
2871
+ // curFreeBusyItem: the current iteration freeBusy, part of manager freeBusys list
2872
+ // start: the insterted freeBusy start
2873
+ // end: the inserted freebusy end
2874
+ var curFreeBusyItem = this;
2875
+ if (curFreeBusyItem.isWithin(start) && curFreeBusyItem.isWithin(end)) {
2876
+ /*
2877
+ we are in case where inserted freebusy fits in curFreeBusyItem:
2878
+ curFreeBusyItem: *-----------------------------*
2879
+ freeBusy: *-------------*
2880
+ obviously, start and end indexes are this item.
2881
+ */
2882
+ startIndex = index;
2883
+ endIndex = index;
2884
+ if (start.getTime() == curFreeBusyItem.getStart().getTime() && end.getTime() == curFreeBusyItem.getEnd().getTime()) {
2885
+ /*
2886
+ in this case, inserted freebusy is exactly curFreeBusyItem:
2887
+ curFreeBusyItem: *-----------------------------*
2888
+ freeBusy: *-----------------------------*
2889
+
2890
+ just replace curFreeBusyItem with freeBusy.
2891
+ */
2892
+ var _f1 = new FreeBusy(freeBusy.getOption());
2893
+ pushNewFreeBusy(_f1);
2894
+ }
2895
+ else if (start.getTime() == curFreeBusyItem.getStart().getTime()) {
2896
+ /*
2897
+ in this case inserted freebusy starts with curFreeBusyItem:
2898
+ curFreeBusyItem: *-----------------------------*
2899
+ freeBusy: *--------------*
2900
+
2901
+ just replace curFreeBusyItem with freeBusy AND the rest.
2902
+ */
2903
+ var _f1 = new FreeBusy(freeBusy.getOption());
2904
+ var _f2 = new FreeBusy(curFreeBusyItem.getOption());
2905
+ _f2.setOption('start', end);
2906
+ pushNewFreeBusy(_f1);
2907
+ pushNewFreeBusy(_f2);
2908
+ }
2909
+ else if (end.getTime() == curFreeBusyItem.getEnd().getTime()) {
2910
+ /*
2911
+ in this case inserted freebusy ends with curFreeBusyItem:
2912
+ curFreeBusyItem: *-----------------------------*
2913
+ freeBusy: *--------------*
2914
+
2915
+ just replace curFreeBusyItem with before part AND freeBusy.
2916
+ */
2917
+ var _f1 = new FreeBusy(curFreeBusyItem.getOption());
2918
+ _f1.setOption('end', start);
2919
+ var _f2 = new FreeBusy(freeBusy.getOption());
2920
+ pushNewFreeBusy(_f1);
2921
+ pushNewFreeBusy(_f2);
2922
+ }
2923
+ else {
2924
+ /*
2925
+ in this case inserted freebusy is within curFreeBusyItem:
2926
+ curFreeBusyItem: *-----------------------------*
2927
+ freeBusy: *--------------*
2928
+
2929
+ just replace curFreeBusyItem with before part AND freeBusy AND the rest.
2930
+ */
2931
+ var _f1 = new FreeBusy(curFreeBusyItem.getOption());
2932
+ var _f2 = new FreeBusy(freeBusy.getOption());
2933
+ var _f3 = new FreeBusy(curFreeBusyItem.getOption());
2934
+ _f1.setOption('end', start);
2935
+ _f3.setOption('start', end);
2936
+ pushNewFreeBusy(_f1);
2937
+ pushNewFreeBusy(_f2);
2938
+ pushNewFreeBusy(_f3);
2939
+ }
2940
+ /*
2941
+ as work is done, no need to go further.
2942
+ return false
2943
+ */
2944
+ return false;
2945
+ }
2946
+ else if (curFreeBusyItem.isWithin(start) && curFreeBusyItem.getEnd().getTime() != start.getTime()) {
2947
+ /*
2948
+ in this case, inserted freebusy starts within curFreeBusyItem:
2949
+ curFreeBusyItem: *----------*
2950
+ freeBusy: *-------------------*
2951
+
2952
+ set start index AND insert before part, we'll insert freebusy later
2953
+ */
2954
+ if (curFreeBusyItem.getStart().getTime() != start.getTime()) {
2955
+ var _f1 = new FreeBusy(curFreeBusyItem.getOption());
2956
+ _f1.setOption('end', start);
2957
+ pushNewFreeBusy(_f1);
2958
+ }
2959
+ startIndex = index;
2960
+ }
2961
+ else if (curFreeBusyItem.isWithin(end) && curFreeBusyItem.getStart().getTime() != end.getTime()) {
2962
+ /*
2963
+ in this case, inserted freebusy starts within curFreeBusyItem:
2964
+ curFreeBusyItem: *----------*
2965
+ freeBusy: *-------------------*
2966
+
2967
+ set end index AND insert freebusy AND insert after part if needed
2968
+ */
2969
+ pushNewFreeBusy(new FreeBusy(freeBusy.getOption()));
2970
+ if (end.getTime() < curFreeBusyItem.getEnd().getTime()) {
2971
+ var _f1 = new FreeBusy(curFreeBusyItem.getOption());
2972
+ _f1.setOption('start', end);
2973
+ pushNewFreeBusy(_f1);
2974
+ }
2975
+ endIndex = index;
2976
+ return false;
2977
+ }
2978
+ });
2979
+ //now compute arguments
2980
+ var tmpFB = this.freeBusys;
2981
+ this.freeBusys = [];
2982
+
2983
+ if (startIndex) {
2984
+ this.freeBusys = this.freeBusys.concat(tmpFB.slice(0, startIndex));
2985
+ }
2986
+ this.freeBusys = this.freeBusys.concat(newFreeBusys);
2987
+ if (endIndex < tmpFB.length) {
2988
+ this.freeBusys = this.freeBusys.concat(tmpFB.slice(endIndex + 1));
2989
+ }
2990
+ /* if(start.getDate() == 1){
2991
+ console.info('insert from '+freeBusy.getStart() +' to '+freeBusy.getEnd());
2992
+ console.log('index from '+ startIndex + ' to ' + endIndex);
2993
+ var str = [];
2994
+ $.each(tmpFB, function(i){str.push(i + ": " + this.getStart().getHours() + ' > ' + this.getEnd().getHours() + ' ' + (this.getOption('free') ? 'free' : 'busy'))});
2995
+ console.log(str.join('\n'));
2996
+
2997
+ console.log('insert');
2998
+ var str = [];
2999
+ $.each(newFreeBusys, function(i){str.push(this.getStart().getHours() + ' > ' + this.getEnd().getHours())});
3000
+ console.log(str.join(', '));
3001
+
3002
+ console.log('results');
3003
+ var str = [];
3004
+ $.each(this.freeBusys, function(i){str.push(i + ": " + this.getStart().getHours() + ' > ' + this.getEnd().getHours() + ' ' + (this.getOption('free') ? 'free' :'busy'))});
3005
+ console.log(str.join('\n'));
3006
+ }*/
3007
+ return this;
3008
+ }
3009
+ });
3010
+
3011
+ })(jQuery);