PageSourceSearch

https://arce.org/wp-content/themes/arce/assets/js/AddCalEvent.js?ver=1790120228

js arce.org collected 2026-09-24 08:44:05 UTC 18,924 bytes, 532 lines download raw bytes

1/* global jQuery */
2/**
3 * AddCalEvent.js
4
5 * @author Mike Sendlakowski, [email protected]
6 *
7 * Adds functionality to a button element to all users to add an event
8 * to various calendar apps
9 *
10 * Usage:
11 * All parameters are optional. However, if you want this to work for Outlook and iCal you must
12 * provide the url that will generate the ics file response
13 *
14 * $(SELECTOR).addcalevent({
15 *    'onclick': true,
16 *    'apps': [1,2,4],
17 *    'ics': '',
18 *    'data': {}
19 * });
20 *
21 * onclick: Open the dropdown on click rather than mouseover (default: false)
22 * data: Event data (see below for format). This can also be set individually as a json string directly on each HTML element.
23 * linkText: Array containing localized or customized text for the calendar links (order is critical)
24 * ics: Url that will generate the server-side ics response based on the event details. Not optional if Outlook or iCal is needed.
25 * apps: Array containing the applications available to the user. (default: [] - which is all apps)
26 *            0 = Outlook
27 *            1 = Google
28 *            2 = Yahoo
29 *            3 = Hotmail
30 *            4 = iCal
31 *
32 * Format of event data object:
33 *
34 * title: '',
35 * desc: '',
36 * location: '',
37 * url: '',         // only available in Yahoo calendars
38 * organizer: {     // only available in Outlook
39 *    name: '',
40 *    email: ''
41 * },
42 * time: {
43 *    start: '',    // 'month day, year 14:30:00'
44 *    end: '',      // 'month day, year 14:30:00'
45 *    zone: '',     // '+tt:tt' - plus or minus from UTC time zone (example: Pacific Daylight Time is '-07:00')
46 *    allday: false
47 * }
48 *
49 */
50
51(function(window, $, undefined) {
52    'use strict';
53
54    // CONSTANTS
55    var EVENT_SHOW = 'AddCalEvent:show',
56    EVENT_ADDED = 'AddCalEvent:add',
57    EVENT_INVALID = 'AddCalEvent:invalid',
58    DEFAULT_EVENT = {
59        title: '',
60        desc: '',
61        location: '',
62        url: '',
63        organizer: {name: '', email: ''},
64        time: {
65            start: '',
66            end: '',
67            zone: '00:00',
68            allday: false
69        }
70    };
71
72    $.addcalevent = function addcalevent(options, element) {
73        this.el = $(element);
74		// Flag the object in the event of a failed creation
75		if (!this._create(options)) {
76			this.failed = true;
77		}
78	};
79
80	$.addcalevent.defaults = {
81        onclick: false,
82        apps: [],
83        ics: '',
84        preVal: true,
85        disabledClass: "dis",
86        linkText: [
87            'Outlook Calendar',
88            'Google Calendar',
89            'Yahoo Calendar',
90            'Hotmail Calendar',
91            'iCal Calendar'
92        ]
93    };
94
95    $.addcalevent.prototype = {
96
97		/*	
98		----------------------------
99		Private methods
100		----------------------------
101		*/
102
103		_create: function(options){
104			this.options = $.extend(true, {}, $.addcalevent.defaults, options);
105            if(this.options.preVal){
106                this._disable(!this._sanitizeData());
107            }
108            this._attach();
109            return true;
110        },
111
112        // add the basic hook to the button
113        _attach: function() {
114            var self = this;
115
116            // handle clicking on a calendar link
117            this.el.on('click', '.ace_opt', function(event){self._click(this, event);});
118
119            // handle opening and closing the dropdown - always available for accessiblity
120            this.el.on('click', function(event){self._clickShow(event);});
121            $(document).on('click', function(){self._hide();});
122
123            // handle opening and closing the dropdown on mouseover
124            if(!this.options.onclick) {
125                this.el.on('mouseover', function(){self._show();});
126                //this.el.on('mouseout', function(){self._hide();});
127            }
128        },
129
130        // onclick handler for button (not calendar links)
131        _clickShow: function(event) {
132            if(this.open) {
133                this._hide();
134            } else {
135                this._show();
136                this.open = true;
137            }
138            event.stopPropagation();
139        },
140        
141        // show the dropdown
142        _show: function() {
143            var self = this;
144            if(!this.dd) {
145                this._createDropdown();
146            }
147            this.dd.stop();
148            this.dd.clearQueue();
149
150            this.dd.show();
151            self.dd.css(self.ddCss);    
152            // if(this.opening) {  // its already started animating, so finish it immediately
153            //     this.dd.show();
154            //     self.dd.css(self.ddCss);               
155            // } else {
156            //     this.dd.show('fast');
157            //     this.opening = true;
158            //     this._eventPublisher(EVENT_SHOW);
159            // }
160        },
161
162        // hide the dropdown
163        _hide: function() {
164            var self = this;
165            if(!this.dd) { // if it doesn't have a dropdown then no need to hide it. duh!
166                return;
167            }
168            this.open = false;
169            this.dd.stop();
170            this.dd.clearQueue();
171            this.dd.hide('fast', function(){    
172                self.opening = false;
173                self.dd.css(self.ddCss); // reset the height and opacity, because the jquery animation was losing it when we call stop
174            });
175        },
176
177        // create the dropdown
178        _createDropdown: function(showOpts) {
179            var isValid = this._sanitizeData();
180            this._disable(!isValid);
181            this.dd = $(this._createHtml(isValid));
182            this.el.append(this.dd);
183            this.ddCss = {
184                height: this.dd.css("height"),
185                opacity: 1
186            }
187        },
188
189        _disable:function(disable){
190            this.el.toggleClass(this.options.disabledClass, disable);
191        },
192
193        // create the html for the dropdown
194        _createHtml: function(isValid) {
195            var styleString = this._getStyle(),
196            optionLinks = '';
197            for(var i=0,j=this.options.linkText.length;i<j;i++) {
198                if(this._isLinkNeeded(i)) {
199                    optionLinks += '<li><a class="ace_opt" data-ace-id="'+ i +'" href="javascript:;">' + this.options.linkText[i] + '</a>';
200                }
201            }
202            return '<ul class="ace_dd" style="">' + optionLinks + '</ul>';
203        },
204
205        // test to see if we need to display this link
206        _isLinkNeeded: function(linkId) {
207            var isOK = false,
208            appLength = this.options.apps.length;
209            if(appLength === 0) {
210                isOK = this._runIcsTest(linkId);
211            } else {
212                for(var y=0,z=this.options.apps.length;y<z;y++) {
213                    if(this.options.apps[y] === linkId) {
214                        isOK = this._runIcsTest(linkId);
215                        break;
216                    }
217                }
218            }
219            return isOK;
220        },
221
222        // only show the outlook and ical links if the ics url is available
223        _runIcsTest: function(linkId) {
224            var isOK = false;
225            if(linkId === 0 || linkId === 4) {
226                if(this.options.ics !== '') {
227                    isOK = true;
228                }
229            } else {
230                isOK = true;
231            }
232            return isOK;
233        },
234
235        // get the absolute position and min-width based on the parent button
236        // could cause issues if the button moves around the page dynamically when it resizes
237        _getStyle: function() {
238            var pos = this.el.position(),
239            left = pos.left + parseFloat(this.el.css('marginLeft')),
240            top = pos.top + this.el.outerHeight() + parseFloat(this.el.css('marginTop')) - 1,
241            minwidth = this.el.outerWidth() - parseFloat(this.el.css('borderLeftWidth'));
242
243            
244            return 'min-width:' + minwidth + 'px;top:' + top + 'px;left:' + left + 'px';
245        },
246
247        // make sure all event data is available and valid 
248        _sanitizeData: function() {
249            var testData = this._getDataFromEl(),
250            isValid = true,
251            zoneRegex = /^[\+\-]?[0-9]{2}:[0-9]{2}/;
252
253            if(!testData) {
254                testData = this.options.data;
255            }
256            this.options.data = $.extend(true, {}, DEFAULT_EVENT, testData);
257            this.options.data.timeObj = {};
258
259            // is the start date valid?
260            this.options.data.timeObj.start = new Date(this.options.data.time.start);
261            if(isNaN(this.options.data.timeObj.start.getTime())) {
262                isValid = false;
263                this._eventPublisher(EVENT_INVALID, {invalidStartDate: this.options.data.time.start});
264            }
265
266            // is the end date valid?
267            this.options.data.timeObj.end = new Date(this.options.data.time.end);
268            if(isNaN(this.options.data.timeObj.end.getTime())) {
269                isValid = false;
270                this._eventPublisher(EVENT_INVALID, {invalidEndDate: this.options.data.time.end});
271            }
272            
273            // is the time zone valid?
274            this._lookupTimeZone();
275            if(this.options.data.time.zone.length < 6) {
276                this.options.data.time.zone = '+' + this.options.data.time.zone;
277            }
278
279            if(!zoneRegex.test(this.options.data.time.zone)) {
280                isValid = false;
281                this._eventPublisher(EVENT_INVALID, {invalidTimeZone: this.options.data.time.zone});
282                this.options.data.time.zone = '+00:00';
283            }
284
285            return isValid;
286        },
287
288        // translates a time zone abbreviation to an offset, as long as
289        // AddCalEventZones.js is loaded. But ideally the zone offset is identified on the server.
290        _lookupTimeZone: function(){
291            if(AddCalEventZones && AddCalEventZones[this.options.data.time.zone]) {
292                this.options.data.time.zone = AddCalEventZones[this.options.data.time.zone];    
293            }
294        },
295
296        // get the data from the html element rather than the plugin config
297        _getDataFromEl: function(){
298            var data = this.el.data('ace');
299            if(typeof data === 'string') {
300                this._eventPublisher(EVENT_INVALID, {invalidDataString: data});
301                data = null;
302            }
303            return data;
304        },
305
306        // update the event options
307        _update: function(options){
308            this.options = $.extend(true, {}, $.addcalevent.defaults, options);
309            if(this.options.preVal){
310                this._disable(!this._sanitizeData());
311            }
312            return true;
313        },
314
315        // an event link was clicked
316        _click: function(item, event) {
317            var id = $(item).data('ace-id');
318            this._addEvent(id);
319            event.stopPropagation();
320            this._hide();
321        },
322
323        // create an 'add to calendar' url and open it in a new window
324        // most of the details were taken from this article:
325        // http://richmarr.wordpress.com/2008/01/07/adding-events-to-users-calendars-part-2-web-calendars/
326        _addEvent: function(calendarLinkType){
327            var url,
328                sameWindow;
329            switch(calendarLinkType) {
330            case 0:     //Outlook
331            case 4:     // iCal
332                url = this._getUrl_ics();
333                sameWindow = true;
334                break;
335            case 1:     // Google
336                url = this._getUrl_google();
337                break;
338            case 2:     // Yahoo
339                url = this._getUrl_yahoo();
340                break;
341            case 3:     // Hotmail
342                url = this._getUrl_hotmail();
343                break;
344            }
345            if(url) {
346                if(sameWindow) {
347                    location.href = url;
348                } else {
349                    window.open(url, 'calendar');                    
350                }
351            }
352            this._eventPublisher(EVENT_ADDED, {calendarLinkType: calendarLinkType, url: url});
353        },
354
355        _getUTCTime: function(dateObj) {
356            var newDateObj = this._adjustToUTC(dateObj, this.options.data.time.zone);
357            return this._getDatePart(newDateObj.getFullYear(),4) + this._getDatePart(newDateObj.getMonth()+1,2) + this._getDatePart(newDateObj.getDate(),2) + 'T' + this._getDatePart(newDateObj.getHours(),2) + this._getDatePart(newDateObj.getMinutes(),2) + this._getDatePart(newDateObj.getSeconds(),2) + 'Z';
358        },
359
360        // return a new date object based on dateObj and the zone
361        _adjustToUTC: function(dateObj, zone){
362            var dateOut = new Date(dateObj),
363            hours, mins;
364
365            if(isNaN(dateOut.getTime())) {
366                return new Date();
367            }
368
369            // adjust to UTC
370            hours = zone.substring(1,3);
371            mins = zone.substring(4,6);
372            if(zone.substring(0,1) === '-') {
373                dateOut.setHours(dateOut.getHours() + (hours-0));
374                dateOut.setMinutes(dateOut.getMinutes() + (mins-0));
375            } else {
376                dateOut.setHours(dateOut.getHours() - hours);
377                dateOut.setMinutes(dateOut.getMinutes() - mins);
378            }
379            return dateOut;
380        },
381
382        _getDatePart: function(part, digits){
383            part = part.toString();
384            while(part.length < digits) {
385                part = '0' + part;
386            }
387            return part;
388        },
389        
390        _getDateDiff: function(startDate, endDate) {
391            var diff = Math.floor((endDate - startDate)/60000),
392            hours = Math.floor(diff/60),
393            mins = diff - (hours * 60);
394            return this._getDatePart(hours,2) + this._getDatePart(mins,2);
395        },
396
397        _getUrl_ics: function() {
398            var data = this.options.data,
399            url = this.options.ics;
400
401            url += (url.indexOf('?') === -1) ? '?' : '&';
402            url += 'title=' + encodeURIComponent(data.title);
403            url += '&desc=' + encodeURIComponent(data.desc);
404            url += '&start=' + this._getUTCTime(data.timeObj.start);
405            url += '&end=' + this._getUTCTime(data.timeObj.end);
406            url += '&loc=' + encodeURIComponent(data.location);
407            url += '&org=' + encodeURIComponent(data.organizer.name);
408            url += '&url=' + encodeURIComponent(data.url);
409            url += '&offset=' + new Date().getTimezoneOffset();
410            return url;
411        },
412
413        _getUrl_google: function() {
414            var data = this.options.data,
415            url = 'http://www.google.com/calendar/event?action=TEMPLATE';
416            url += '&text=' + encodeURIComponent(data.title);
417            url += '&details=' + encodeURIComponent(data.desc);
418            url += '&location=' + encodeURIComponent(data.location);
419            url += '&dates=' + encodeURIComponent(this._getUTCTime(data.timeObj.start) + '/' + this._getUTCTime(data.timeObj.end));  // time needs to be sent as UTC and let Google handle converting to local
420            url += '&sprop=website:' + encodeURIComponent(data.url);
421            url += '&sprop=name:' + encodeURIComponent(data.organizer.name);
422            return url;
423        },
424        _getUrl_yahoo: function() {
425            var data = this.options.data,
426            url = 'http://calendar.yahoo.com?v=60';
427            url += '&TITLE=' + encodeURIComponent(data.title);
428            url += '&DESC=' + encodeURIComponent(data.desc);
429            url += '&URL=' + encodeURIComponent(data.url);
430            url += '&in_loc=' + encodeURIComponent(data.location);
431            url += '&ST=' + this._getUTCTime(data.timeObj.start);
432            url += '&DUR=' + this._getDateDiff(data.timeObj.start, data.timeObj.end);
433            return url;
434        },
435        _getUrl_hotmail: function() {
436            var data = this.options.data,
437            url = 'http://calendar.live.com/calendar/calendar.aspx?rru=addevent';
438            url += '&summary=' + encodeURIComponent(data.title);
439            url += '&location=' + encodeURIComponent(data.location);
440            url += '&dtstart=' + this._getUTCTime(data.timeObj.start);
441            url += '&dtend=' + this._getUTCTime(data.timeObj.end);
442            url += '&description=' + encodeURIComponent(data.desc);
443            return url;
444        },
445
446        _eventPublisher: function(evt, evtObj) {
447            evtObj = evtObj || {};
448            evtObj.buttonId = this.el.attr('id');
449            evtObj.data = this.options.data;
450            $(document).trigger(evt, evtObj);
451            if(window.console){
452                window.console.log(evt);
453                window.console.log(evtObj);
454            }
455        },
456
457		/*	
458		----------------------------
459		Public methods
460		----------------------------
461		*/
462
463		getEventData: function(returnObj) {
464            this._sanitizeData();
465            returnObj.data = this.options.data;
466        },
467
468        update: function(options){
469            this._update(options);
470        }
471    };
472
473
474	/*	
475	----------------------------
476	Function
477	----------------------------
478	*/
479
480	$.fn.addcalevent = function addcalevent_init(options) {
481
482		var thisCall = typeof options;
483
484		switch (thisCall) {
485
486            // allow users to call a specific public method 
487        case 'string':
488            var args = Array.prototype.slice.call(arguments, 1);
489            try{
490                this.each(function () {
491                    var instance = $.data(this, 'addcalevent');
492                    if (!instance) {
493                        throw 'Method ' + options + ' cannot be called until addcalevent is setup';
494                    }
495                    if (!$.isFunction(instance[options]) || options.charAt(0) === '_') {
496                        throw 'No such public method ' + options + ' for addcalevent';
497                    }
498                    // no errors!
499                    instance[options].apply(instance, args);
500                });
501            } catch(err){
502                if(window.console){
503                    window.console.log(err);
504                }
505                return false;
506            }
507            break;
508
509			// attempt to create
510        case 'undefined':
511        case 'object':
512            this.each(function () {
513                var instance = $.data(this, 'addcalevent');
514                if (instance) {
515                    // update options of current instance
516                    instance.update(options);
517                } else {
518                    // initialize new instance
519                    instance = new $.addcalevent(options, this);
520                    // don't attach if instantiation failed
521                    if (!instance.failed) {
522                        $.data(this, 'addcalevent', instance);
523                    }
524                }
525            });
526            break;
527        
528        }
529        return this;
530    };
531
532})(window, jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.