jquery-week-calendar 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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);