commit e3aefb487960aa85f33713024b4c346df53334dc Author: Harry Bomrah Date: Tue Dec 16 19:40:15 2014 +0800 initial commit diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..2d1ab8d --- /dev/null +++ b/Gemfile @@ -0,0 +1,17 @@ +source "https://rubygems.org" + +# Declare your gem's dependencies in calendar.gemspec. +# Bundler will treat runtime dependencies like base dependencies, and +# development dependencies will be added by default to the :development group. +gemspec + +# jquery-rails is used by the dummy application +gem "jquery-rails" + +# Declare any dependencies that are still in development here instead of in +# your gemspec. These might include edge Rails or gems from your path or +# Git. Remember to move these dependencies to your gemspec before releasing +# your gem to rubygems.org. + +# To use debugger +# gem 'debugger' diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..ea966ec --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright 2014 YOURNAME + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README b/README new file mode 100644 index 0000000..e69de29 diff --git a/README.rdoc b/README.rdoc new file mode 100644 index 0000000..86e6359 --- /dev/null +++ b/README.rdoc @@ -0,0 +1,3 @@ += Calendar + +This project rocks and uses MIT-LICENSE. \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..c1a55bc --- /dev/null +++ b/Rakefile @@ -0,0 +1,38 @@ +#!/usr/bin/env rake +begin + require 'bundler/setup' +rescue LoadError + puts 'You must `gem install bundler` and `bundle install` to run rake tasks' +end +begin + require 'rdoc/task' +rescue LoadError + require 'rdoc/rdoc' + require 'rake/rdoctask' + RDoc::Task = Rake::RDocTask +end + +RDoc::Task.new(:rdoc) do |rdoc| + rdoc.rdoc_dir = 'rdoc' + rdoc.title = 'Calendar' + rdoc.options << '--line-numbers' + rdoc.rdoc_files.include('README.rdoc') + rdoc.rdoc_files.include('lib/**/*.rb') +end + + + + +Bundler::GemHelper.install_tasks + +require 'rake/testtask' + +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = false +end + + +task :default => :test diff --git a/app/assets/images/animated-overlay.gif b/app/assets/images/animated-overlay.gif new file mode 100644 index 0000000..d441f75 Binary files /dev/null and b/app/assets/images/animated-overlay.gif differ diff --git a/app/assets/images/calendar/.gitkeep b/app/assets/images/calendar/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png b/app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 0000000..c06ecaa Binary files /dev/null and b/app/assets/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/app/assets/images/ui-bg_flat_75_ffffff_40x100.png b/app/assets/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 0000000..db28c75 Binary files /dev/null and b/app/assets/images/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/app/assets/images/ui-bg_glass_55_fbf9ee_1x400.png b/app/assets/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 0000000..137b9d2 Binary files /dev/null and b/app/assets/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/app/assets/images/ui-bg_glass_65_ffffff_1x400.png b/app/assets/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 0000000..0dc2feb Binary files /dev/null and b/app/assets/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/app/assets/images/ui-bg_glass_75_dadada_1x400.png b/app/assets/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 0000000..c6b94b6 Binary files /dev/null and b/app/assets/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/app/assets/images/ui-bg_glass_75_e6e6e6_1x400.png b/app/assets/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 0000000..d99dd51 Binary files /dev/null and b/app/assets/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png b/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 0000000..8e40834 Binary files /dev/null and b/app/assets/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/app/assets/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/app/assets/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 0000000..4359bc3 Binary files /dev/null and b/app/assets/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/app/assets/images/ui-icons_222222_256x240.png b/app/assets/images/ui-icons_222222_256x240.png new file mode 100644 index 0000000..43bf16e Binary files /dev/null and b/app/assets/images/ui-icons_222222_256x240.png differ diff --git a/app/assets/images/ui-icons_2e83ff_256x240.png b/app/assets/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 0000000..4af3763 Binary files /dev/null and b/app/assets/images/ui-icons_2e83ff_256x240.png differ diff --git a/app/assets/images/ui-icons_454545_256x240.png b/app/assets/images/ui-icons_454545_256x240.png new file mode 100644 index 0000000..77628c6 Binary files /dev/null and b/app/assets/images/ui-icons_454545_256x240.png differ diff --git a/app/assets/images/ui-icons_888888_256x240.png b/app/assets/images/ui-icons_888888_256x240.png new file mode 100644 index 0000000..dac466f Binary files /dev/null and b/app/assets/images/ui-icons_888888_256x240.png differ diff --git a/app/assets/images/ui-icons_cd0a0a_256x240.png b/app/assets/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 0000000..61827f0 Binary files /dev/null and b/app/assets/images/ui-icons_cd0a0a_256x240.png differ diff --git a/app/assets/javascripts/bootstrap-datetimepicker.js b/app/assets/javascripts/bootstrap-datetimepicker.js new file mode 100644 index 0000000..d15823a --- /dev/null +++ b/app/assets/javascripts/bootstrap-datetimepicker.js @@ -0,0 +1,1284 @@ +/** + * @license + * ========================================================= + * bootstrap-datetimepicker.js + * http://www.eyecon.ro/bootstrap-datepicker + * ========================================================= + * Copyright 2012 Stefan Petre + * + * Contributions: + * - Andrew Rowls + * - Thiago de Arruda + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ========================================================= + */ + +(function($) { + + // Picker object + var smartPhone = (window.orientation != undefined); + var DateTimePicker = function(element, options) { + this.id = dpgId++; + this.init(element, options); + }; + + DateTimePicker.prototype = { + constructor: DateTimePicker, + + init: function(element, options) { + var icon; + if (!(options.pickTime || options.pickDate)) + throw new Error('Must choose at least one picker'); + this.options = options; + this.$element = $(element); + this.language = options.language in dates ? options.language : 'en' + this.pickDate = options.pickDate; + this.pickTime = options.pickTime; + this.isInput = this.$element.is('input'); + // this.component = this.$element.is('.input-prepend') ? this.$element.find('.iconbtn') : false; + this.component = this.$element.is('.input-append') ? this.$element.find('.iconbtn') : false; + this.clearDate = this.$element.is('.input-append') ? this.$element.find('.clearDate') : false; + this.format = options.format; + if (!this.format) { + if (this.isInput) this.format = this.$element.data('format'); + else this.format = this.$element.find('input').data('format'); + if (!this.format) this.format = 'MM/dd/yyyy'; + } + this._compileFormat(); + if (this.component) { + icon = this.component.find('i'); + } + if (this.pickTime) { + if (icon && icon.length) this.timeIcon = icon.data('time-icon'); + if (!this.timeIcon) this.timeIcon = 'icon-time'; + icon.addClass(this.timeIcon); + } + if (this.pickDate) { + if (icon && icon.length) this.dateIcon = icon.data('date-icon'); + if (!this.dateIcon) this.dateIcon = 'icon-calendar'; + icon.removeClass(this.timeIcon); + icon.addClass(this.dateIcon); + } + this.widget = $(getTemplate(this.format, this.timeIcon, options.pickDate, options.pickTime, options.pick12HourFormat)).appendTo('body'); + this.minViewMode = options.minViewMode||this.$element.data('date-minviewmode')||0; + if (typeof this.minViewMode === 'string') { + switch (this.minViewMode) { + case 'months': + this.minViewMode = 1; + break; + case 'years': + this.minViewMode = 2; + break; + default: + this.minViewMode = 0; + break; + } + } + this.viewMode = options.viewMode||this.$element.data('date-viewmode')||0; + if (typeof this.viewMode === 'string') { + switch (this.viewMode) { + case 'months': + this.viewMode = 1; + break; + case 'years': + this.viewMode = 2; + break; + default: + this.viewMode = 0; + break; + } + } + this.startViewMode = this.viewMode; + this.weekStart = options.weekStart||this.$element.data('date-weekstart')||0; + this.weekEnd = this.weekStart === 0 ? 6 : this.weekStart - 1; + this.fillDow(); + this.fillMonths(); + this.fillHours(); + this.fillMinutes(); + this.fillSeconds(); + this.update(); + this.showMode(); + this._attachDatePickerEvents(); + }, + + show: function(e) { + this.widget.show(); + this.height = this.component ? this.component.outerHeight() : this.$element.outerHeight(); + this.width = this.component ? this.component.outerWidth() : this.$element.outerWidth(); + this.place(this.options.place); + this.$element.trigger({ + type: 'show', + date: this._date + }); + this._attachDatePickerGlobalEvents(); + if (e) { + e.stopPropagation(); + e.preventDefault(); + } + }, + + hide: function() { + // Ignore event if in the middle of a picker transition + var collapse = this.widget.find('.collapse') + for (var i = 0; i < collapse.length; i++) { + var collapseData = collapse.eq(i).data('collapse'); + if (collapseData && collapseData.transitioning) + return; + } + this.widget.hide(); + this.viewMode = this.startViewMode; + this.showMode(); + // this.set(); + this.$element.trigger({ + type: 'hide', + date: this._date + }); + this.actions.showPicker.call(this); + this._detachDatePickerGlobalEvents(); + }, + + set: function() { + var formatted = ''; + if (!this._unset) formatted = this.formatDate(this._date); + if (!this.isInput) { + if (this.component){ + var input = this.$element.find('input'); + input.val(formatted); + this._resetMaskPos(input); + } + this.$element.data('date', formatted); + } else { + this.$element.val(formatted); + this._resetMaskPos(this.$element); + } + }, + + setValue: function(newDate) { + if (!newDate) { + this._unset = true; + } else { + this._unset = false; + } + if (typeof newDate === 'string') { + this._date = this.parseDate(newDate); + } else { + this._date = new Date(newDate); + } + this.set(); + this.viewDate = UTCDate(this._date.getUTCFullYear(), this._date.getUTCMonth(), 1, 0, 0, 0, 0); + this.fillDate(); + this.fillTime(); + }, + + getDate: function() { + if (this._unset) return null; + return new Date(this._date.valueOf()); + }, + + setDate: function(date) { + if (!date) this.setValue(null); + else this.setValue(date.valueOf()); + }, + + getLocalDate: function() { + if (this._unset) return null; + var d = this._date; + return new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), + d.getUTCHours(), d.getUTCMinutes(), d.getUTCSeconds(), d.getUTCMilliseconds()); + }, + + setLocalDate: function(localDate) { + if (!localDate) this.setValue(null); + else + this.setValue(Date.UTC( + localDate.getFullYear(), + localDate.getMonth(), + localDate.getDate(), + localDate.getHours(), + localDate.getMinutes(), + localDate.getSeconds(), + localDate.getMilliseconds())); + }, + + place: function(pos){ + var offset = this.component ? this.component.offset() : this.$element.offset(), + input = this.$element.find('input'), + widget_height = this.widget.height(); + + if(pos === "bottom"){ + this.widget.css({ + top: offset.top + this.height, + left: offset.left - input.outerWidth() - this.width, + }); + }else if(pos === "top"){ + this.widget.css({ + top: offset.top - 13 - widget_height, + left: offset.left - input.outerWidth() - this.width, + }); + } + }, + + notifyChange: function(){ + this.$element.trigger({ + type: 'changeDate', + date: this.getDate(), + localDate: this.getLocalDate() + }); + }, + + update: function(newDate){ + var dateStr = newDate; + if (!dateStr) { + if (this.isInput) { + dateStr = this.$element.val(); + } else { + dateStr = this.$element.find('input').val(); + } + if (!dateStr) { + var tmp = new Date() + this._date = UTCDate(tmp.getFullYear(), + tmp.getMonth(), + tmp.getDate(), + tmp.getHours(), + tmp.getMinutes(), + tmp.getSeconds(), + tmp.getMilliseconds()) + } else { + this._date = this.parseDate(dateStr); + } + } + this.viewDate = UTCDate(this._date.getUTCFullYear(), this._date.getUTCMonth(), 1, 0, 0, 0, 0); + this.fillDate(); + this.fillTime(); + }, + + fillDow: function() { + var dowCnt = this.weekStart; + var html = ''; + while (dowCnt < this.weekStart + 7) { + html += '' + dates[this.language].daysMin[(dowCnt++) % 7] + ''; + } + html += ''; + this.widget.find('.datepicker-days thead').append(html); + }, + + fillMonths: function() { + var html = ''; + var i = 0 + while (i < 12) { + html += '' + dates[this.language].monthsShort[i++] + ''; + } + this.widget.find('.datepicker-months td').append(html); + }, + + fillDate: function() { + var year = this.viewDate.getUTCFullYear(); + var month = this.viewDate.getUTCMonth(); + var currentDate = UTCDate( + this._date.getUTCFullYear(), + this._date.getUTCMonth(), + this._date.getUTCDate(), + 0, 0, 0, 0 + ); + this.widget.find('.datepicker-days th:eq(1)').text( + dates[this.language].months[month] + ' ' + year); + var prevMonth = UTCDate(year, month-1, 28, 0, 0, 0, 0); + var day = DPGlobal.getDaysInMonth( + prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); + prevMonth.setUTCDate(day); + prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7) % 7); + var nextMonth = new Date(prevMonth.valueOf()); + nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); + nextMonth = nextMonth.valueOf(); + var html = []; + var clsName; + while (prevMonth.valueOf() < nextMonth) { + if (prevMonth.getUTCDay() === this.weekStart) { + html.push(''); + } + clsName = ''; + if (prevMonth.getUTCFullYear() < year || + (prevMonth.getUTCFullYear() == year && + prevMonth.getUTCMonth() < month)) { + clsName += ' old'; + } else if (prevMonth.getUTCFullYear() > year || + (prevMonth.getUTCFullYear() == year && + prevMonth.getUTCMonth() > month)) { + clsName += ' new'; + } + if (prevMonth.valueOf() === currentDate.valueOf()) { + clsName += ' active'; + } + html.push('' + prevMonth.getUTCDate() + ''); + if (prevMonth.getUTCDay() === this.weekEnd) { + html.push(''); + } + prevMonth.setUTCDate(prevMonth.getUTCDate() + 1); + } + this.widget.find('.datepicker-days tbody').empty().append(html.join('')); + var currentYear = this._date.getUTCFullYear(); + + var months = this.widget.find('.datepicker-months').find( + 'th:eq(1)').text(year).end().find('span').removeClass('active'); + if (currentYear === year) { + months.eq(this._date.getUTCMonth()).addClass('active'); + } + + html = ''; + year = parseInt(year/10, 10) * 10; + var yearCont = this.widget.find('.datepicker-years').find( + 'th:eq(1)').text(year + '-' + (year + 9)).end().find('td'); + year -= 1; + for (var i = -1; i < 11; i++) { + html += '' + year + ''; + year += 1; + } + yearCont.html(html); + }, + + fillHours: function() { + var table = this.widget.find( + '.timepicker .timepicker-hours table'); + table.parent().hide(); + var html = ''; + if (this.options.pick12HourFormat) { + var current = 1; + for (var i = 0; i < 3; i += 1) { + html += ''; + for (var j = 0; j < 4; j += 1) { + var c = current.toString(); + html += '' + padLeft(c, 2, '0') + ''; + current++; + } + html += '' + } + } else { + var current = 0; + for (var i = 0; i < 6; i += 1) { + html += ''; + for (var j = 0; j < 4; j += 1) { + var c = current.toString(); + html += '' + padLeft(c, 2, '0') + ''; + current++; + } + html += '' + } + } + table.html(html); + }, + + fillMinutes: function() { + var table = this.widget.find( + '.timepicker .timepicker-minutes table'); + table.parent().hide(); + var html = ''; + var current = 0; + for (var i = 0; i < 3; i++) { + html += ''; + for (var j = 0; j < 4; j += 1) { + var c = current.toString(); + html += '' + padLeft(c, 2, '0') + ''; + current += 5; + } + html += ''; + } + table.html(html); + }, + + fillSeconds: function() { + var table = this.widget.find( + '.timepicker .timepicker-seconds table'); + table.parent().hide(); + var html = ''; + var current = 0; + for (var i = 0; i < 5; i++) { + html += ''; + for (var j = 0; j < 4; j += 1) { + var c = current.toString(); + html += '' + padLeft(c, 2, '0') + ''; + current += 3; + } + html += ''; + } + table.html(html); + }, + + fillTime: function() { + if (!this._date) + return; + var timeComponents = this.widget.find('.timepicker span[data-time-component]'); + var table = timeComponents.closest('table'); + var is12HourFormat = this.options.pick12HourFormat; + var hour = this._date.getUTCHours(); + var period = 'AM'; + if (is12HourFormat) { + if (hour >= 12) period = 'PM'; + if (hour === 0) hour = 12; + else if (hour != 12) hour = hour % 12; + this.widget.find( + '.timepicker [data-action=togglePeriod]').text(period); + } + hour = padLeft(hour.toString(), 2, '0'); + var minute = padLeft(this._date.getUTCMinutes().toString(), 2, '0'); + var second = padLeft(this._date.getUTCSeconds().toString(), 2, '0'); + timeComponents.filter('[data-time-component=hours]').text(hour); + timeComponents.filter('[data-time-component=minutes]').text(minute); + timeComponents.filter('[data-time-component=seconds]').text(second); + }, + + click: function(e) { + e.stopPropagation(); + e.preventDefault(); + var target = $(e.target).closest('span, td, th'); + if (target.length === 1) { + switch(target[0].nodeName.toLowerCase()) { + case 'th': + switch(target[0].className) { + case 'switch': + this.showMode(1); + break; + case 'prev': + case 'next': + var vd = this.viewDate; + var navFnc; + var step; + if(this.format == 'yyyy') { + navFnc = YGlobal.modes[this.viewMode].navFnc + step = YGlobal.modes[this.viewMode].navStep + } else if(this.format == 'yyyy/MM') { + navFnc = YMGlobal.modes[this.viewMode].navFnc + step = YMGlobal.modes[this.viewMode].navStep + } else { + navFnc = DPGlobal.modes[this.viewMode].navFnc + step = DPGlobal.modes[this.viewMode].navStep + }; + if (target[0].className === 'prev') step = step * -1; + vd['set' + navFnc](vd['get' + navFnc]() + step); + this.fillDate(); + this.set(); + break; + } + break; + case 'span': + if (target.is('.month')) { + var month = target.parent().find('span').index(target); + this.viewDate.setUTCMonth(month); + } else { + var year = parseInt(target.text(), 10) || 0; + this.viewDate.setUTCFullYear(year); + } + // if (this.viewMode !== 0) { + this._date = UTCDate( + this.viewDate.getUTCFullYear(), + this.viewDate.getUTCMonth(), + this.viewDate.getUTCDate(), + this._date.getUTCHours(), + this._date.getUTCMinutes(), + this._date.getUTCSeconds(), + this._date.getUTCMilliseconds() + ); + this.notifyChange(); + // } + this.showMode(-1); + this.fillDate(); + this.set(); + break; + case 'td': + if (target.is('.day')) { + var day = parseInt(target.text(), 10) || 1; + var month = this.viewDate.getUTCMonth(); + var year = this.viewDate.getUTCFullYear(); + if (target.is('.old')) { + if (month === 0) { + month = 11; + year -= 1; + } else { + month -= 1; + } + } else if (target.is('.new')) { + if (month == 11) { + month = 0; + year += 1; + } else { + month += 1; + } + } + this._date = UTCDate( + year, month, day, + this._date.getUTCHours(), + this._date.getUTCMinutes(), + this._date.getUTCSeconds(), + this._date.getUTCMilliseconds() + ); + this.viewDate = UTCDate( + year, month, Math.min(28, day) , 0, 0, 0, 0); + this.fillDate(); + this.set(); + this.notifyChange(); + // this.widget.hide(); + } + break; + } + } + }, + + actions: { + incrementHours: function(e) { + this._date.setUTCHours(this._date.getUTCHours() + 1); + }, + + incrementMinutes: function(e) { + this._date.setUTCMinutes(this._date.getUTCMinutes() + 1); + }, + + // incrementSeconds: function(e) { + // this._date.setUTCSeconds(this._date.getUTCSeconds() + 1); + // }, + + decrementHours: function(e) { + this._date.setUTCHours(this._date.getUTCHours() - 1); + }, + + decrementMinutes: function(e) { + this._date.setUTCMinutes(this._date.getUTCMinutes() - 1); + }, + + // decrementSeconds: function(e) { + // this._date.setUTCSeconds(this._date.getUTCSeconds() - 1); + // }, + + togglePeriod: function(e) { + var hour = this._date.getUTCHours(); + if (hour >= 12) hour -= 12; + else hour += 12; + this._date.setUTCHours(hour); + }, + + showPicker: function() { + this.widget.find('.timepicker > div:not(.timepicker-picker)').hide(); + this.widget.find('.timepicker .timepicker-picker').show(); + }, + + showHours: function() { + this.widget.find('.timepicker .timepicker-picker').hide(); + this.widget.find('.timepicker .timepicker-hours').show(); + }, + + showMinutes: function() { + this.widget.find('.timepicker .timepicker-picker').hide(); + this.widget.find('.timepicker .timepicker-minutes').show(); + }, + + // showSeconds: function() { + // this.widget.find('.timepicker .timepicker-picker').hide(); + // this.widget.find('.timepicker .timepicker-seconds').show(); + // }, + + selectHour: function(e) { + var tgt = $(e.target); + var value = parseInt(tgt.text(), 10); + if (this.options.pick12HourFormat) { + var current = this._date.getUTCHours(); + if (current >= 12) { + if (value != 12) value = (value + 12) % 24; + } else { + if (value === 12) value = 0; + else value = value % 12; + } + } + this._date.setUTCHours(value); + this.actions.showPicker.call(this); + }, + + selectMinute: function(e) { + var tgt = $(e.target); + var value = parseInt(tgt.text(), 10); + this._date.setUTCMinutes(value); + this.actions.showPicker.call(this); + }, + + // selectSecond: function(e) { + // var tgt = $(e.target); + // var value = parseInt(tgt.text(), 10); + // this._date.setUTCSeconds(value); + // this.actions.showPicker.call(this); + // } + }, + + doAction: function(e) { + e.stopPropagation(); + e.preventDefault(); + if (!this._date) this._date = UTCDate(1970, 0, 0, 0, 0, 0, 0); + var action = $(e.currentTarget).data('action'); + var rv = this.actions[action].apply(this, arguments); + this.set(); + this.fillTime(); + this.notifyChange(); + return rv; + }, + + stopEvent: function(e) { + e.stopPropagation(); + e.preventDefault(); + }, + + // part of the following code was taken from + // http://cloud.github.com/downloads/digitalBush/jquery.maskedinput/jquery.maskedinput-1.3.js + keydown: function(e) { + var self = this, k = e.which, input = $(e.target); + if (k == 8 || k == 46) { + // backspace and delete cause the maskPosition + // to be recalculated + setTimeout(function() { + self._resetMaskPos(input); + }); + } + }, + + keypress: function(e) { + var k = e.which; + if (k == 8 || k == 46) { + // For those browsers which will trigger + // keypress on backspace/delete + return; + } + var input = $(e.target); + var c = String.fromCharCode(k); + var val = input.val() || ''; + val += c; + var mask = this._mask[this._maskPos]; + if (!mask) { + return false; + } + if (mask.end != val.length) { + return; + } + if (!mask.pattern.test(val.slice(mask.start))) { + val = val.slice(0, val.length - 1); + while ((mask = this._mask[this._maskPos]) && mask.character) { + val += mask.character; + // advance mask position past static + // part + this._maskPos++; + } + val += c; + if (mask.end != val.length) { + input.val(val); + return false; + } else { + if (!mask.pattern.test(val.slice(mask.start))) { + input.val(val.slice(0, mask.start)); + return false; + } else { + input.val(val); + this._maskPos++; + return false; + } + } + } else { + this._maskPos++; + } + }, + + change: function(e) { + var input = $(e.target); + var val = input.val(); + if (this._formatPattern.test(val)) { + this.update(); + this.setValue(this._date.getTime()); + this.notifyChange(); + this.set(); + } else if (val && val.trim()) { + this.setValue(this._date.getTime()); + if (this._date) this.set(); + else input.val(''); + } else { + if (this._date) { + this.setValue(null); + // unset the date when the input is + // erased + this.notifyChange(); + } + } + this._resetMaskPos(input); + }, + + clear: function(e) { + if (this.isInput) this.$element.val(null); + else this.$element.find('input').val(null); + }, + + showMode: function(dir) { + if (dir) { + if(this.format == 'yyyy') { + this.viewMode = Math.max(this.minViewMode, Math.min(0, this.viewMode + dir)); + } else if(this.format == 'yyyy/MM') { + this.viewMode = Math.max(this.minViewMode, Math.min(1, this.viewMode + dir)); + } else { + this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir)); + } + } + var clsName = null; + if(this.format == 'yyyy') { + clsName = '.datepicker-'+YGlobal.modes[this.viewMode].clsName + } else if(this.format == 'yyyy/MM') { + clsName = '.datepicker-'+YMGlobal.modes[this.viewMode].clsName + } else { + clsName = '.datepicker-'+DPGlobal.modes[this.viewMode].clsName + }; + this.widget.find('.datepicker > div').hide().filter(clsName).show(); + }, + + destroy: function() { + this._detachDatePickerEvents(); + this._detachDatePickerGlobalEvents(); + this.widget.remove(); + this.$element.removeData('datetimepicker'); + this.component.removeData('datetimepicker'); + }, + + formatDate: function(d) { + return this.format.replace(formatReplacer, function(match) { + var methodName, property, rv, len = match.length; + if (match === 'ms') + len = 1; + property = dateFormatComponents[match].property + if (property === 'Hours12') { + rv = d.getUTCHours(); + if (rv === 0) rv = 12; + else if (rv !== 12) rv = rv % 12; + } else if (property === 'Period12') { + if (d.getUTCHours() >= 12) return 'PM'; + else return 'AM'; + } else { + methodName = 'get' + property; + rv = d[methodName](); + } + if (methodName === 'getUTCMonth') rv = rv + 1; + if (methodName === 'getUTCYear') rv = rv + 1900 - 2000; + return padLeft(rv.toString(), len, '0'); + }); + }, + + parseDate: function(str) { + var match, i, property, methodName, value, parsed = {}; + if (!(match = this._formatPattern.exec(str))) + return null; + for (i = 1; i < match.length; i++) { + property = this._propertiesByIndex[i]; + if (!property) + continue; + value = match[i]; + if (/^\d+$/.test(value)) + value = parseInt(value, 10); + parsed[property] = value; + } + return this._finishParsingDate(parsed); + }, + + _resetMaskPos: function(input) { + var val = input.val(); + for (var i = 0; i < this._mask.length; i++) { + if (this._mask[i].end > val.length) { + // If the mask has ended then jump to + // the next + this._maskPos = i; + break; + } else if (this._mask[i].end === val.length) { + this._maskPos = i + 1; + break; + } + } + }, + + _finishParsingDate: function(parsed) { + var year, month, date, hours, minutes, seconds, milliseconds; + year = parsed.UTCFullYear; + if (parsed.UTCYear) year = 2000 + parsed.UTCYear; + if (!year) year = 1970; + if (parsed.UTCMonth) month = parsed.UTCMonth - 1; + else month = 0; + date = parsed.UTCDate || 1; + hours = parsed.UTCHours || 0; + minutes = parsed.UTCMinutes || 0; + seconds = parsed.UTCSeconds || 0; + milliseconds = parsed.UTCMilliseconds || 0; + if (parsed.Hours12) { + hours = parsed.Hours12; + } + if (parsed.Period12) { + if (/pm/i.test(parsed.Period12)) { + if (hours != 12) hours = (hours + 12) % 24; + } else { + hours = hours % 12; + } + } + return UTCDate(year, month, date, hours, minutes, seconds, milliseconds); + }, + + _compileFormat: function () { + var match, component, components = [], mask = [], + str = this.format, propertiesByIndex = {}, i = 0, pos = 0; + while (match = formatComponent.exec(str)) { + component = match[0]; + if (component in dateFormatComponents) { + i++; + propertiesByIndex[i] = dateFormatComponents[component].property; + components.push('\\s*' + dateFormatComponents[component].getPattern( + this) + '\\s*'); + mask.push({ + pattern: new RegExp(dateFormatComponents[component].getPattern( + this)), + property: dateFormatComponents[component].property, + start: pos, + end: pos += component.length + }); + } + else { + components.push(escapeRegExp(component)); + mask.push({ + pattern: new RegExp(escapeRegExp(component)), + character: component, + start: pos, + end: ++pos + }); + } + str = str.slice(component.length); + } + this._mask = mask; + this._maskPos = 0; + this._formatPattern = new RegExp( + '^\\s*' + components.join('') + '\\s*$'); + this._propertiesByIndex = propertiesByIndex; + }, + + _attachDatePickerEvents: function() { + var self = this; + // this handles date picker clicks + this.widget.on('click', '.datepicker *', $.proxy(this.click, this)); + // this handles time picker clicks + this.widget.on('click', '[data-action]', $.proxy(this.doAction, this)); + this.widget.on('mousedown', $.proxy(this.stopEvent, this)); + this.clearDate.on('click', $.proxy(this.clear, this)); + if (this.pickDate && this.pickTime) { + this.widget.on('click.togglePicker', '.accordion-toggle', function(e) { + e.stopPropagation(); + var $this = $(this); + var $parent = $this.closest('ul'); + var expanded = $parent.find('.collapse.in'); + var closed = $parent.find('.collapse:not(.in)'); + + if (expanded && expanded.length) { + var collapseData = expanded.data('collapse'); + if (collapseData && collapseData.transitioning) return; + expanded.collapse('hide'); + closed.collapse('show') + $this.find('i').toggleClass(self.timeIcon + ' ' + self.dateIcon); + self.$element.find('.iconbtn i').toggleClass(self.timeIcon + ' ' + self.dateIcon); + } + }); + } + if (this.isInput) { + this.$element.on({ + 'focus': $.proxy(this.show, this), + 'blur': $.proxy(this.hide, this), + 'change': $.proxy(this.change, this), + }); + if (this.options.maskInput) { + this.$element.on({ + 'keydown': $.proxy(this.keydown, this), + 'keypress': $.proxy(this.keypress, this) + }); + } + } else { + this.$element.on({ + 'focus': $.proxy(this.show, this), + 'blur': $.proxy(this.hide, this), + 'change': $.proxy(this.change, this), + }, 'input'); + if (this.options.maskInput) { + this.$element.on({ + 'keydown': $.proxy(this.keydown, this), + 'keypress': $.proxy(this.keypress, this) + }, 'input'); + } + if (this.component){ + this.component.on('click', $.proxy(this.show, this)); + } else { + this.$element.on('click', $.proxy(this.show, this)); + } + } + }, + + _attachDatePickerGlobalEvents: function() { + $(window).on( + 'resize.datetimepicker' + this.id, $.proxy(this.place, this)); + if (!this.isInput) { + $(document).on( + 'mousedown.datetimepicker' + this.id, $.proxy(this.hide, this)); + } + }, + + _detachDatePickerEvents: function() { + this.widget.off('click', '.datepicker *', this.click); + this.widget.off('click', '[data-action]'); + this.widget.off('mousedown', this.stopEvent); + if (this.pickDate && this.pickTime) { + this.widget.off('click.togglePicker'); + } + if (this.isInput) { + this.$element.off({ + 'focus': this.show, + 'change': this.change, + }); + if (this.options.maskInput) { + this.$element.off({ + 'keydown': this.keydown, + 'keypress': this.keypress + }); + } + } else { + this.$element.off({ + 'change': this.change, + }, 'input'); + if (this.options.maskInput) { + this.$element.off({ + 'keydown': this.keydown, + 'keypress': this.keypress + }, 'input'); + } + if (this.component){ + this.component.off('click', this.show); + } else { + this.$element.off('click', this.show); + } + } + }, + + _detachDatePickerGlobalEvents: function () { + $(window).off('resize.datetimepicker' + this.id); + if (!this.isInput) { + $(document).off('mousedown.datetimepicker' + this.id); + } + } + }; + + $.fn.datetimepicker = function ( option, val ) { + return this.each(function () { + var $this = $(this), + data = $this.data('datetimepicker'), + options = typeof option === 'object' && option; + if (!data) { + $this.data('datetimepicker', (data = new DateTimePicker( + this, $.extend({}, $.fn.datetimepicker.defaults,options)))); + } + if (typeof option === 'string') data[option](val); + }); + }; + + $.fn.datetimepicker.defaults = { + maskInput: true, + pickDate: true, + pickTime: true, + pick12HourFormat: false, + place: "bottom" + }; + $.fn.datetimepicker.Constructor = DateTimePicker; + var dpgId = 0; + var dates = $.fn.datetimepicker.dates = { + en: { + days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday", "Sunday"], + daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], + months: ["January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December"], + monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", + "Aug", "Sep", "Oct", "Nov", "Dec"] + } + }; + + var dateFormatComponents = { + dd: {property: 'UTCDate', getPattern: function() { return '(0?[1-9]|[1-2][0-9]|3[0-1])\\b';}}, + MM: {property: 'UTCMonth', getPattern: function() {return '(0?[1-9]|1[0-2])\\b';}}, + yy: {property: 'UTCYear', getPattern: function() {return '(\\d{2})\\b'}}, + yyyy: {property: 'UTCFullYear', getPattern: function() {return '(\\d{4})\\b';}}, + hh: {property: 'UTCHours', getPattern: function() {return '(0?[0-9]|1[0-9]|2[0-3])\\b';}}, + mm: {property: 'UTCMinutes', getPattern: function() {return '(0?[0-9]|[1-5][0-9])\\b';}}, + ss: {property: 'UTCSeconds', getPattern: function() {return '(0?[0-9]|[1-5][0-9])\\b';}}, + ms: {property: 'UTCMilliseconds', getPattern: function() {return '([0-9]{1,3})\\b';}}, + HH: {property: 'Hours12', getPattern: function() {return '(0?[1-9]|1[0-2])\\b';}}, + PP: {property: 'Period12', getPattern: function() {return '(AM|PM|am|pm|Am|aM|Pm|pM)\\b';}} + }; + + var keys = []; + for (var k in dateFormatComponents) keys.push(k); + keys[keys.length - 1] += '\\b'; + keys.push('.'); + + var formatComponent = new RegExp(keys.join('\\b|')); + keys.pop(); + var formatReplacer = new RegExp(keys.join('\\b|'), 'g'); + + function escapeRegExp(str) { + // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + + function padLeft(s, l, c) { + if (l < s.length) return s; + else return Array(l - s.length + 1).join(c || ' ') + s; + } + + function getTemplate(format, timeIcon, pickDate, pickTime, is12Hours) { + if (pickDate && pickTime) { + return ( + '' + ); + } else if (pickTime) { + return ( + '' + ); + } else { + if(format == 'yyyy') { + return( + '' + ) + } else if(format == 'yyyy/MM') { + return ( + '' + ); + } else { + return ( + '' + ); + } + } + } + + function UTCDate() { + return new Date(Date.UTC.apply(Date, arguments)); + } + + var DPGlobal = { + modes: [ + { + clsName: 'days', + navFnc: 'UTCMonth', + navStep: 1 + }, + { + clsName: 'months', + navFnc: 'UTCFullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'UTCFullYear', + navStep: 10 + } + ], + isLeapYear: function (year) { + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) + }, + getDaysInMonth: function (year, month) { + return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] + }, + headTemplate: + '' + + '' + + '‹' + + '' + + '›' + + '' + + '', + contTemplate: '' + }; + var YMGlobal = { + modes: [ + { + clsName: 'months', + navFnc: 'UTCFullYear', + navStep: 1 + }, + { + clsName: 'years', + navFnc: 'UTCFullYear', + navStep: 10 + } + ], + isLeapYear: function (year) { + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) + }, + getDaysInMonth: function (year, month) { + return [31, (YMGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] + }, + headTemplate: + '' + + '' + + '‹' + + '' + + '›' + + '' + + '', + contTemplate: '' + }; + var YGlobal = { + modes: [ + { + clsName: 'years', + navFnc: 'UTCFullYear', + navStep: 10 + } + ], + isLeapYear: function (year) { + return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)) + }, + headTemplate: + '' + + '' + + '‹' + + '' + + '›' + + '' + + '', + contTemplate: '' + }; + DPGlobal.template = + '
' + + '' + + DPGlobal.headTemplate + + '' + + '
' + + '
' + + '
' + + '' + + DPGlobal.headTemplate + + DPGlobal.contTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + '
'+ + '
'; + YMGlobal.template = + '
' + + '' + + DPGlobal.headTemplate + + DPGlobal.contTemplate+ + '
'+ + '
'+ + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + '
'+ + '
'; + YGlobal.template = + '
'+ + ''+ + DPGlobal.headTemplate+ + DPGlobal.contTemplate+ + '
'+ + '
'; + var TPGlobal = { + hourTemplate: '', + minuteTemplate: '', + // secondTemplate: '', + }; + TPGlobal.getTemplate = function(is12Hours) { + return ( + '
' + + '' + + '' + + '' + + '' + + '' + + // '' + + // '' + + // (is12Hours ? '' : '') + + '' + + '' + + ' ' + + '' + + ' ' + + // '' + + // '' + + (is12Hours ? + '' + + '' : '') + + '' + + '' + + '' + + '' + + '' + + // '' + + // '' + + // (is12Hours ? '' : '') + + '' + + '
' + TPGlobal.hourTemplate + ':' + TPGlobal.minuteTemplate + ':' + TPGlobal.secondTemplate + '' + + '' + + '
' + + '
' + + '
' + + '' + + '
'+ + '
'+ + '
' + + '' + + '
'+ + // '
'+ + // '
' + + // '' + + // '
'+ + '
' + ); + } + + +})(window.jQuery) diff --git a/app/assets/javascripts/calendar.js b/app/assets/javascripts/calendar.js new file mode 100644 index 0000000..0bf80e3 --- /dev/null +++ b/app/assets/javascripts/calendar.js @@ -0,0 +1,742 @@ +var Calendar = function(dom){ + + c = this; + this.create_event_btn = $("#create_event_btn"); + this.event_create_space = $("#event_create_space"); + this.title = $("#current_title"); + this.calendar = $(dom); + this.nextBtn = $("#next_month_btn"); + this.prevBtn = $("#prev_month_btn"); + this.todayBtn = $("#today_btn"); + this.modeBtns = $(".calendar_mode button"); + this.refreshBtn = $("#refresh_btn"); + this.mousePosition = {}; + this.dialog = new EventDialog(c); + this.loading = $('#calendar-loading'); + this.success_event = null; + this.agenda_space = $("#calendar_agenda"); + this.currentView = "month"; + this.navigation = $("#navigation"); + this.rangeSelection = $("#range_selection"); + var agendaView = new AgendaView(c); + var loadeventsonviewchange = false; + + this.initialize = function(){ + var date = new Date(); + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + var change_event = function(_event, delta) { + _event.end = (_event.end ? _event.end : _event.start); + var s = $.fullCalendar.parseDate(c.calendar.find(".fc-view table tbody td:first").data("date")); + var e = $.fullCalendar.parseDate(c.calendar.find(".fc-view table tbody td:first").data("date")); + $.ajax({ + url: "/admin/calendars/"+_event.id, + // data: {event:{id:_event.id,start:_event.start,end: _event.end,_s:Math.round(+s / 1000), _e:Math.round(+e / 1000)}}, + data: {event:{id:_event.id,start:_event.start,end: _event.end}}, + type: 'put' , + datatype: 'JSON', + error: function(jqXHR, textStatus, errorThrown) {}, + success: function(data) { + console.log('event was success updated'); + } + }); + } + var success_event = function(data,allDay,type,addbtn){ + c.dialog.dismiss(); + + c.event_create_space.html(data); + var create_space_height = c.event_create_space.height(), + create_space_width = c.event_create_space.width(); + + if((create_space_height + c.mousePosition["y"]) >= $(window).height()){ + c.event_create_space.css("top",(c.mousePosition["y"] - create_space_height) + "px"); + }else{ + c.event_create_space.css("top",c.mousePosition["y"] + "px"); + } + + if((create_space_width + c.mousePosition["x"]) >= $(window).width()){ + c.event_create_space.css("left",(c.mousePosition["x"] - create_space_width) + "px"); + }else{ + c.event_create_space.css("left",c.mousePosition["x"] + "px"); + } + + if(addbtn){ + c.event_create_space.css({"right":"8px","bottom":"50px","left":"auto","top":"auto"}); + }else{ + c.event_create_space.css({"right":"","bottom":""}); + } + + c.event_create_space.show(); + var dtpick = $('.datetimepick'); + var pickers = new Array(); + var checked = ($("#all_day_check").is(":checked") ? true : false); + var checked_function = function(c){ + if(c){ + for(i in pickers){ + var input = pickers[i].find("input"); + input.val(input.val().split(" ")[0]); + } + }else{ + for(i in pickers){ + var d = new Date(); + var input = pickers[i].find("input"); + if(input.val()) + input.val(input.val() + " " + d.getHours() + ":" + d.getMinutes()); + } + } + } + var repeat_function = function(){ + if(c.event_create_space.find("#event_period").val() == c.event_create_space.find("#event_period option:eq(0)").val()){ + c.event_create_space.find("#event_frequency").attr("disabled","disabled"); + }else{ + c.event_create_space.find("#event_frequency").removeAttr("disabled"); + } + } + dtpick.each(function() { + var $data = $(this).data(); + var options = { + format: $data.dateFormat, + pickTime: $data.picktime, + language: $data.language, + place: "top" + } + pickers.push( + $(this).datetimepicker(options) + ); + }); + $("a.btn-close").one("click",function(){ + c.event_create_space.html("").hide(); + return false; + }); + + $("#all_day_check").click(function(){ + if($(this).is(":checked")){ + checked = true; + }else{ + checked = false; + } + checked_function(checked); + }); + $("#recurring_checkbox").click(function(){ + if($(this).is(":checked")) + $("#recurring_panel").show(); + else + $("#recurring_panel").hide(); + + }) + $('form[data-remote]').bind("ajax:success",function(evt, data, status){ + c.event_create_space.html("").hide(); + if(type == "new") + c.renderEvent(data); + if(type == "edit") + c.calendar.fullCalendar("refetchEvents"); + }); + c.event_create_space.find("#event_period").change(function(){ + repeat_function(); + }) + for(i in pickers){ + pickers[i].on("changeDate",function(e){ + if(checked){ + var input = $(this).find("input"); + input.val(input.val().split(" ")[0]); + } + }) + } + repeat_function(); + if(allDay) + checked_function(checked); + } + c.success_event = success_event; + var dview = (c.currentView == "agenda" ? "month" : c.currentView); + c.calendar.fullCalendar({ + editable: true, + selectable: true, + events: "/admin/calendars/", + eventResize: change_event, + eventDrop: change_event , + header: false, + default: dview, + height: $(window).height() - 315, + loading: function(bool) { + if (bool) c.loading.css("left",($(window).width()/2 - 60) + "px").show(); + else c.loading.hide(); + }, + windowResize : function(view){ + view.setHeight($(window).height() - 315); + c.calendar.fullCalendar("refetchEvents"); + }, + viewDisplay: function(view) { + c.title.html(view.title); + }, + eventClick: function(calEvent, e, view) { + c.event_create_space.html("").hide(); + c.dialog.dismiss(); + c.dialog.inflate(calEvent); + c.dialog.show({"x":e.originalEvent.clientX,"y":e.originalEvent.clientY}); + }, + select : function(startDate, endDate, allDay, jsEvent, view){ + var start = new Date(startDate), + end = new Date(endDate), + startString = start.getFullYear() + "/"+ (start.getMonth() + 1 > 9 ? start.getMonth() + 1 : "0" + (start.getMonth() + 1)) + "/" + (start.getDate() > 9 ? start.getDate() : "0" + start.getDate()), + endString = end.getFullYear() + "/" + (end.getMonth() + 1 > 9 ? end.getMonth() + 1 : "0" + (end.getMonth() + 1)) + "/" + (end.getDate() > 9 ? end.getDate() : "0" + end.getDate()); + + if(!allDay){ + startString += " " + (start.getHours() > 9 ? start.getHours() : "0" + start.getHours()) + ":" + (start.getMinutes() > 9 ? start.getMinutes() : "0" + start.getMinutes()); + endString += " " + (end.getHours() > 9 ? end.getHours() : "0" + end.getHours()) + ":" + (end.getMinutes() > 9 ? end.getMinutes() : "0" + end.getMinutes()); + }else{ + startString += " " + start.getHours() + ":" + start.getMinutes(); + endString += " " + end.getMinutes() + ":" + end.getMinutes(); + } + + $.ajax({ + type : "get", + url : c.create_event_btn.attr("href"), + data : {"startDate":startString,"endDate":endString,"allDay":allDay}, + success : function(data){ + success_event(data,allDay,"new"); + } + }) + } + }); + c.create_event_btn.click(function(){ + $.ajax({ + type : "get", + url : $(this).attr("href"), + success : function(data){ + success_event(data,false,"new",true); + } + }) + return false; + }); + c.nextBtn.click(function(){ + c.dialog.dismiss(); + c.calendar.fullCalendar('next'); + }); + c.prevBtn.click(function(){ + c.dialog.dismiss(); + c.calendar.fullCalendar('prev'); + }); + c.todayBtn.click(function(){ + c.dialog.dismiss(); + c.calendar.fullCalendar('today'); + }); + c.modeBtns.click(function(){ + c.dialog.dismiss(); + c.event_create_space.html("").hide(); + toggleViews($(this).data("mode")); + }); + c.refreshBtn.click(function(){ + c.dialog.dismiss(); + if(c.currentView == "agenda") + agendaView.refresh(); + else + c.calendar.fullCalendar("refetchEvents"); + }); + c.calendar.mouseup(function(e){ + c.mousePosition = {"x" : e.pageX, "y" : e.pageY}; + }) + + var toggleViews = function(view){ + c.modeBtns.removeClass("active"); + c.modeBtns.each(function(){ + if ($(this).data("mode") == view) + $(this).addClass("active"); + }) + if(view != "agenda"){ + if(c.currentView == "agenda"){ + $("#sec1").addClass("span3").removeClass("span7"); + $("#sec2").show(); + $("#sec3").addClass("span4").removeClass("span5"); + agendaView.hide(); + } + c.calendar.fullCalendar('changeView',view); + }else{ + $("#sec1").addClass("span7").removeClass("span3"); + $("#sec2").hide(); + $("#sec3").addClass("span5").removeClass("span4"); + agendaView.inflate(); + } + c.currentView = view; + if(loadeventsonviewchange){ + c.calendar.fullCalendar("refetchEvents"); + loadeventsonviewchange = false; + } + } + if(c.currentView == "agenda"){toggleViews("agenda");loadeventsonviewchange = true;} + $(document).on("DOMMouseScroll mousewheel", function(e){ + if(c.calendar.fullCalendar("getView").name == "month"){ + if(!c.isFormVisible()){ + c.dialog.dismiss(); + if(/Firefox/i.test(navigator.userAgent)){ + if(e.originalEvent.detail == 1){ + c.calendar.fullCalendar('next'); + }else if(e.originalEvent.detail == -1){ + c.calendar.fullCalendar('prev'); + } + }else{ + if(e.originalEvent.wheelDelta /120 > 0) + c.calendar.fullCalendar('prev'); + else + c.calendar.fullCalendar('next'); + } + } + } + }); + + } + + this.isFormVisible = function(){ + return (c.event_create_space.html() == "" ? false : true); + } + + this.renderEvent = function(eventStick){ + if(eventStick.recurring == true){ + c.calendar.fullCalendar("refetchEvents"); + }else{ + c.calendar.fullCalendar("renderEvent",eventStick); + } + } + + this.updateEvent = function(eventStick){ + c.calendar.fullCalendar("updateEvent",eventStick); + } + + this.deleteEvent = function(delete_url,_id){ + $.ajax({ + type : "delete", + url : delete_url, + success : function(){ + c.calendar.fullCalendar("removeEvents",[_id]); + c.dialog.dismiss(); + } + }) + } + + this.editEvent = function(edit_url,allDay){ + $.ajax({ + type : "get", + url : edit_url, + success : function(data){ + c.success_event(data,allDay,"edit",true); + c.dialog.dismiss(); + } + }) + } + + $(document).ready(function() { + c.initialize(); + }); +} + +var EventDialog = function(calendar,event){ + _t = this; + var event_quick_view = null; + var template = ""; + var _this_event = null; + this.inflate = function(_event){ + if(!_event) throw new UserException("EventStick can't be null!"); + _this_event = _event; + var start_time = "", + end_time = "", + time_string = null; + + if(_event.allDay){ + start_time = (/00:00:00/i.test(_event._start.toLocaleString()) ? $.fullCalendar.formatDate(_event._start,"ddd MMM dd, yyyy") : $.fullCalendar.formatDate(_event._start,"ddd MMM dd, yyyy hh:mm")); + if(_event._end) + end_time = (/00:00:00/i.test(_event._end.toLocaleString()) ? $.fullCalendar.formatDate(_event._end,"ddd MMM dd, yyyy") : $.fullCalendar.formatDate(_event._end,"ddd MMM dd, yyyy hh:mm")); + time_string = (_event._start === _event._end || !_event._end ? start_time : start_time + " - " + end_time); + }else{ + var reg = new RegExp(/ [0-9][0-9]:[0-9][0-9]:[0-9][0-9]/), + stime = _event._start.toLocaleString().split(",")[1], + etime = _event._end.toLocaleString().split(",")[1]; + + start_time = _event._start.toLocaleString().replace(stime,""); + end_time = _event._end.toLocaleString().replace(etime,""); + + stime = stime.substr(0,stime.length - 3); + etime = etime.substr(0,etime.length - 3); + + time_string = (start_time === end_time ? start_time + " " + stime + " - " + etime : start_time + " " + stime + " - " + end_time + " " +etime ); + } + event_quick_view = $(''); + template = ''; + } + + this.show = function(pos){ + if(pos){ + var pos = getPosition(pos); + event_quick_view.css({"left":pos.x+"px","top":pos.y+"px"}); + } + event_quick_view.html(template).appendTo("body").show(); + event_quick_view.find(".event-close-btn").one("click",function(){_t.dismiss();}); + event_quick_view.find("a.delete").one("click",function(){calendar.deleteEvent(_this_event.delete_url,_this_event._id);return false;}); + event_quick_view.find("a.edit").one("click",function(){calendar.editEvent(_this_event.edit_url,_this_event.allDay);return false;}); + } + + this.dismiss = function(){ + if(event_quick_view) + event_quick_view.remove(); + } + + var getPosition = function(pos){ + var x = pos.x, + y = pos.y, + winheight = $(window).height(); + if((x + event_quick_view.width()) > $(window).width()){ + x = x - event_quick_view.width(); + } + if((y + event_quick_view.height()) > winheight){ + y = y - event_quick_view.height(); + } + return {"x":x,"y":y}; + } + + if(event) + _t.inflate(event); +} + +var UserException = function(message) { + this.message = message; + this.name = "UserException"; + this.toString = function(){ + return this.message; + } +} + +var AgendaView = function(calendar){ + var av = this; + var _calendar = calendar; + var agenda_space = _calendar.agenda_space; + var today = new Date(); + var minDifference = 6; + var start_month = today.getMonth(); + var start_year = today.getFullYear(); + var end_month = ((start_month + minDifference) > 11 ? (start_month + minDifference) - 11 : start_month + minDifference); + var end_year = ((start_month + minDifference) > 11 ? start_year+1 : start_year); + var monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December']; + var month_template = '

SunMonTueWedThuFriSat
'; + + var event_list_template = '
No events for this month.
'; + + var head_template = '
'; + + var event_template = "
"; + + var cache = false; + var show_event_clicked = false; + + this.refresh = function(){ + av.inflate(true); + } + + this.inflate = function(forceInflation){ + loading(true); + _calendar.calendar.hide(); + _calendar.navigation.hide(); + + if(!forceInflation){ + if(cache){ + av.show(); + loading(false); + return; + } + } + + agenda_space.empty(); + if(!show_event_clicked){ + _calendar.rangeSelection.empty(); + _calendar.rangeSelection.append(renderHead().html()).show(); + _calendar.rangeSelection.find("button#show_events").click(function(){ + show_event_clicked = true; + start_month = parseInt($("select[name=start_month]").val()); + end_month = parseInt($("select[name=end_month]").val()); + start_year = parseInt($("select[name=start_year]").val()); + end_year = parseInt($("select[name=end_year]").val()); + av.inflate(true); + }) + } + show_event_clicked = false; + eventsManager(); + var s = start_month, + e = end_month + y = start_year; + e = (e > s && start_year == end_year? e : e + 11); + if(end_year > start_year) + e = e + ((end_year - start_year -1) * 12); + else e--; + for(var i = s;i <= e+1; i++){ + var m = new Month(s,y); + s++; + if(s > 11){ + s = 0; + y++; + } + if(e == 0) + agenda_space.text("Invalid Range of Dates.") + else + agenda_space.append(m.monthDom); + } + + loading(false); + } + + this.hide = function(){ + cache = true; + _calendar.rangeSelection.hide(); + agenda_space.hide(); + _calendar.navigation.show(); + _calendar.calendar.show(); + } + + this.show = function(){ + _calendar.rangeSelection.show(); + agenda_space.show(); + } + var copyObject = function(x){ + return x.clone(); + } + var eventsManager = function(){ + var url = "/admin/calendars/agenda", + sd = new Date(start_year,start_month,1), + ed = new Date(end_year,end_month+1,0), + usd = Math.round(sd/1000), + ued = Math.round(ed/1000); + $.ajax({ + type : "get", + url : url, + data : {"agenda_start":sd.toLocaleString(),"agenda_end":ed.toLocaleString(),"unix_start":usd,"unix_end":ued}, + success : function(events){ + $.each(events,function(i,e){ + var ed = eventDom(e), + s = new Date(e.start), + e = new Date(e.end), + e_m = ((e.getMonth() > s.getMonth() || s.getMonth() == e.getMonth()) && s.getFullYear() == e.getFullYear() ? e.getMonth() : e.getMonth() + 12) + s_m = s.getMonth(), + s_y = s.getFullYear(); + if(e.getFullYear() > s.getFullYear()) + e_m = e_m + ((e.getFullYear() - s.getFullYear() -1) * 12); + for(var i = s_m; i < e_m + 1; i++){ + var temp_ed = copyObject(ed); + var list = agenda_space.find("div[data-month="+s_m+"][data-year="+s_y+"] table.event_list tbody"); + list.append(temp_ed); + s_m++; + if(s_m > 11){ + s_m = 0; + s_y++; + } + } + if(s.getDate() == e.getDate() && s.getMonth() == s.getMonth() && e.getFullYear() == e.getFullYear()){ + var td = agenda_space.find("td[data-date-node="+s.getDate()+"-"+s.getMonth()+"-"+s.getFullYear()+"]"); + td.addClass("has_event"); + }else{ + var timeDiff = Math.abs(e.getTime() - s.getTime()), + diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)), + c_m = s.getMonth(), + c_d = s.getDate(), + c_y = s.getFullYear(), + end_of_c_month = new Date(s.getFullYear(),s.getMonth()+1,0).getDate(); + + for(var i = 0; i <= diffDays; i++){ + var td = agenda_space.find("td[data-date-node="+c_d+"-"+c_m+"-"+c_y+"]"); + td.addClass("has_event"); + c_d++; + if(c_d > end_of_c_month){ + c_d = 1; + c_m++; + if(c_m > 11){ + c_m = 0; + c_y++; + } + } + } + } + }) + agenda_space.find("table.event_list tbody").each(function(){ + if($(this).find("tr").length > 1) + $(this).find("td.no_events").parent().remove(); + }) + } + }) + var eventDom = function(event){ + var e_t = $(event_template), + s = new Date(event.start), + e = new Date(event.end), + dateFormat = ""; + if(s.getDate() == e.getDate() && s.getMonth() == s.getMonth() && e.getFullYear() == e.getFullYear()) + dateFormat = $.fullCalendar.formatDate(s, "ddd dd"); + else + dateFormat = $.fullCalendar.formatDates(s, e, "ddd dd, MMM - {ddd dd, MMM}"); + e_t.find("th").text(dateFormat); + e_t.find("td.event_time").text((event.allDay ? "All Day" : $.fullCalendar.formatDate(s, "hh:mm"))); + e_t.find("div.event").text(event.title).css("color",event.color); + return e_t; + } + + } + + var loading = function(bool) { + if (bool) _calendar.loading.css("left",($(window).width()/2 - 60) + "px").show(); + else _calendar.loading.hide(); + } + + var renderHead = function(){ + var head = $(head_template); + var start_month_select = head.find("select[name=start_month]"); + for(var i = 0; i < 12; i++){ + var option = $(""); + if(i == start_month) + option.attr("selected","selected"); + start_month_select.append(option); + } + var end_month_select = head.find("select[name=end_month]"); + for(var i = 0; i < 12; i++){ + var option = $(""); + if(i == end_month) + option.attr("selected","selected"); + end_month_select.append(option); + } + + var start_year_select = head.find("select[name=start_year]"); + var y = start_year - 5; + for(var i = 0; i < 10; i++){ + var option = $(""); + if(y == start_year) + option.attr("selected","selected"); + start_year_select.append(option); + y++; + } + var end_year_select = head.find("select[name=end_year]"); + y = start_year - 5; + for(var i = 0; i < 10; i++){ + var option = $(""); + if(y == end_year) + option.attr("selected","selected"); + end_year_select.append(option); + y++; + } + return head; + } + var Month = function(month,year){ + _this = this; + this.monthDom = $("
"); + var template = $(month_template); + var list_template = $(event_list_template); + var firstDay = new Date(year,month,1); + var lastDay = new Date(year,month+1,0); + var last_inserted_date = 1; + + var renderMonth = function(){ + var num_of_rows = getNumberOfRows(year,month) + for(var i = 0; i < num_of_rows; i++){ + var tr = null; + if(i == 0) + tr = makeRow("first"); + else if(i == (num_of_rows - 1)){ + tr = makeRow("last"); + }else{ + tr = makeRow("middle"); + } + if(tr == null){ + break; + } + template.find("table.table tbody").append(tr); + template.find("h4").text(monthNames[firstDay.getMonth()] + " - " + firstDay.getFullYear()); + } + _this.monthDom.append(template); + _this.monthDom.append(list_template); + } + + function getNumberOfRows(year, month) { + var day = 1, + sat_counter = 0, + sunday_counter = 0, + date = new Date(year, month, day); + + while(date.getMonth() === month) { + if(date.getDay() === 0) { + sunday_counter++; + }else if(date.getDay() === 6) { + sat_counter++; + } + day++; + date = new Date(year, month, day); + } + return (sunday_counter == 5 && sat_counter == 5 ? 6 : 5); + } + + var makeRow = function(position){ + if(last_inserted_date <= lastDay.getDate()){ + var row = $(""); + switch (position){ + case "first": + for(var i = 0;i < 7;i++){ + var td = $(""); + if(i >= firstDay.getDay()){ + td.text(last_inserted_date); + td.attr("data-date-node",last_inserted_date+"-"+firstDay.getMonth()+"-"+firstDay.getFullYear()); + last_inserted_date++; + } + row.append(td); + } + break; + case "middle": + for(var i = 0;i < 7;i++){ + var td = $(""); + td.text(last_inserted_date); + td.attr("data-date-node",last_inserted_date+"-"+firstDay.getMonth()+"-"+firstDay.getFullYear()); + last_inserted_date++; + row.append(td); + } + break; + case "last": + for(var i = 0;i < 7;i++){ + var td = $(""); + if(i <= lastDay.getDay()){ + td.text(last_inserted_date); + td.attr("data-date-node",last_inserted_date+"-"+firstDay.getMonth()+"-"+firstDay.getFullYear()); + last_inserted_date++; + } + row.append(td); + } + break; + } + }else{ + var row = null; + } + return row; + } + + renderMonth(); + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/calendar/.gitkeep b/app/assets/javascripts/calendar/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/javascripts/calendar/application.js b/app/assets/javascripts/calendar/application.js new file mode 100644 index 0000000..0081161 --- /dev/null +++ b/app/assets/javascripts/calendar/application.js @@ -0,0 +1,42 @@ +//= require_tree . +//= require fullcalendar + +$(document).ready(function() { + + + var date = new Date(); + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + change_event = function(_event, delta) { + console.log("evento" + _event.id); + $.ajax({ + url: "events/"+_event.id+'.json', + data: {event:{id:_event.id,start:_event.start,end: _event.end}}, + method: 'PUT' , + datatype: 'JSON', + error: function(jqXHR, textStatus, errorThrown) { + console.log(textStatus); + }, + success: function(data) { + console.log('event was success updated'); + } + }); + } + $('#calendar').fullCalendar({ + header: { + left: 'prev,next today', + center: 'title', + right: 'month,agendaWeek,agendaDay' + }, + editable: true, + events: "events/", + eventResize: change_event, + eventDrop: change_event , + + loading: function(bool) { + if (bool) $('#loading').show(); + else $('#loading').hide(); + } + }); +}); diff --git a/app/assets/javascripts/calendar_frontend.js b/app/assets/javascripts/calendar_frontend.js new file mode 100644 index 0000000..0b0767e --- /dev/null +++ b/app/assets/javascripts/calendar_frontend.js @@ -0,0 +1,528 @@ +var Calendar = function(dom,page_id){ + + c = this; + this.title = $("#current_title"); + this.calendar = $(dom); + this.nextBtn = $("#next_month_btn"); + this.prevBtn = $("#prev_month_btn"); + this.todayBtn = $("#today_btn"); + this.modeBtns = $(".calendar_mode button"); + this.refreshBtn = $("#refresh_btn"); + this.dialog = new EventDialog(c); + this.loading = $('#calendar-loading'); + this.agenda_space = $("#calendar_agenda"); + this.currentView = "month"; + this.page_id = page_id; + this.navigation = $("#navigation"); + this.rangeSelection = $("#range_selection"); + var agendaView = new AgendaView(c); + var loadeventsonviewchange = false; + this.initialize = function(){ + var date = new Date(); + var d = date.getDate(); + var m = date.getMonth(); + var y = date.getFullYear(); + var dview = (c.currentView == "agenda" ? "month" : c.currentView); + c.calendar.fullCalendar({ + editable: false, + selectable: false, + events: "/xhr/calendars/events?page_id="+c.page_id, + header: false, + default: dview, + height: $("body").height() - 141, + loading: function(bool) { + if (bool) c.loading.css("left",($(window).width()/2 - 60) + "px").show(); + else c.loading.hide(); + }, + windowResize : function(view){ + view.setHeight($("body").height() - 141); + c.calendar.fullCalendar("refetchEvents"); + }, + viewDisplay: function(view) { + c.title.html(view.title); + }, + eventClick: function(calEvent, e, view) { + c.dialog.dismiss(); + c.dialog.inflate(calEvent); + c.dialog.show({"x":e.originalEvent.clientX,"y":e.originalEvent.clientY}); + } + }); + + c.nextBtn.click(function(){ + c.dialog.dismiss(); + c.calendar.fullCalendar('next'); + }); + c.prevBtn.click(function(){ + c.dialog.dismiss(); + c.calendar.fullCalendar('prev'); + }); + c.todayBtn.click(function(){ + c.dialog.dismiss(); + c.calendar.fullCalendar('today'); + }); + c.modeBtns.click(function(){ + c.dialog.dismiss(); + toggleViews($(this).data("mode")); + }); + c.refreshBtn.click(function(){ + c.dialog.dismiss(); + if(c.currentView == "agenda") + agendaView.refresh(); + else + c.calendar.fullCalendar("refetchEvents"); + }); + + var toggleViews = function(view){ + c.modeBtns.removeClass("active"); + c.modeBtns.each(function(){ + if ($(this).data("mode") == view) + $(this).addClass("active"); + }) + if(view != "agenda"){ + if(c.currentView == "agenda"){ + $("#sec1").addClass("span3").removeClass("span7"); + $("#sec2").show(); + $("#sec3").addClass("span4").removeClass("span5"); + agendaView.hide(); + } + c.calendar.fullCalendar('changeView',view); + }else{ + $("#sec1").addClass("span7").removeClass("span3"); + $("#sec2").hide(); + $("#sec3").addClass("span5").removeClass("span4"); + agendaView.inflate(); + } + c.currentView = view; + if(loadeventsonviewchange){ + c.calendar.fullCalendar("refetchEvents"); + loadeventsonviewchange = false; + } + } + if(c.currentView == "agenda"){toggleViews("agenda");loadeventsonviewchange = true;} + + } + + this.renderEvent = function(eventStick){ + if(eventStick.recurring == true) + c.calendar.fullCalendar("refetchEvents"); + else + c.calendar.fullCalendar("renderEvent",eventStick); + } + + + $(document).ready(function() { + c.initialize(); + }); +} + +var EventDialog = function(calendar,event){ + _t = this; + var event_quick_view = null; + var template = ""; + var _this_event = null; + this.inflate = function(_event){ + if(!_event) throw new UserException("EventStick can't be null!"); + _this_event = _event; + var start_time = "", + end_time = "", + time_string = null; + + if(_event.allDay){ + start_time = (/00:00:00/i.test(_event._start.toLocaleString()) ? $.fullCalendar.formatDate(_event._start,"ddd MMM dd, yyyy") : $.fullCalendar.formatDate(_event._start,"ddd MMM dd, yyyy hh:mm")); + if(_event._end) + end_time = (/00:00:00/i.test(_event._end.toLocaleString()) ? $.fullCalendar.formatDate(_event._end,"ddd MMM dd, yyyy") : $.fullCalendar.formatDate(_event._end,"ddd MMM dd, yyyy hh:mm")); + time_string = (_event._start === _event._end || !_event._end ? start_time : start_time + " - " + end_time); + }else{ + var reg = new RegExp(/ [0-9][0-9]:[0-9][0-9]:[0-9][0-9]/), + stime = _event._start.toLocaleString().split(",")[1], + etime = _event._end.toLocaleString().split(",")[1]; + + start_time = _event._start.toLocaleString().replace(stime,","); + end_time = _event._end.toLocaleString().replace(etime,","); + + stime = stime.substr(0,stime.length - 3); + etime = etime.substr(0,etime.length - 3); + + time_string = (start_time === end_time ? start_time + " " + stime + " - " + etime : start_time + " " + stime + " - " + end_time + " " +etime ); + } + event_quick_view = $(''); + template = ''; + } + + this.show = function(pos){ + if(pos){ + var pos = getPosition(pos); + event_quick_view.css({"left":pos.x+"px","top":pos.y+"px"}); + } + event_quick_view.html(template).appendTo("body").show(); + event_quick_view.find(".event-close-btn").one("click",function(){_t.dismiss();}); + event_quick_view.find("a.delete").one("click",function(){calendar.deleteEvent(_this_event.delete_url,_this_event._id);return false;}); + event_quick_view.find("a.edit").one("click",function(){calendar.editEvent(_this_event.edit_url,_this_event.allDay);return false;}); + } + + this.dismiss = function(){ + if(event_quick_view) + event_quick_view.remove(); + } + + var getPosition = function(pos){ + var x = pos.x, + y = pos.y, + winheight = $(window).height(); + if((x + event_quick_view.width()) > $(window).width()){ + x = x - event_quick_view.width(); + } + if((y + event_quick_view.height()) > winheight){ + y = y - event_quick_view.height(); + } + return {"x":x,"y":y}; + } + + if(event) + _t.inflate(event); +} + +var UserException = function(message) { + this.message = message; + this.name = "UserException"; + this.toString = function(){ + return this.message; + } +} + +var AgendaView = function(calendar){ + var av = this; + var _calendar = calendar; + var agenda_space = _calendar.agenda_space; + var today = new Date(); + var minDifference = 6; + var start_month = today.getMonth(); + var start_year = today.getFullYear(); + var end_month = ((start_month + minDifference) > 11 ? (start_month + minDifference) - 11 : start_month + minDifference); + var end_year = ((start_month + minDifference) > 11 ? start_year+1 : start_year); + var monthNames = ['January','February','March','April','May','June','July','August','September','October','November','December']; + var month_template = '

SunMonTueWedThuFriSat
'; + + var event_list_template = '
No events for this month.
'; + + var head_template = '
'; + + var event_template = "
"; + + var cache = false; + var show_event_clicked = false; + + this.refresh = function(){ + av.inflate(true); + } + + this.inflate = function(forceInflation){ + loading(true); + _calendar.calendar.hide(); + _calendar.navigation.hide(); + + if(!forceInflation){ + if(cache){ + av.show(); + loading(false); + return; + } + } + + agenda_space.empty(); + if(!show_event_clicked){ + _calendar.rangeSelection.empty(); + _calendar.rangeSelection.append(renderHead().html()).show(); + _calendar.rangeSelection.find("button#show_events").click(function(){ + show_event_clicked = true; + start_month = parseInt($("select[name=start_month]").val()); + end_month = parseInt($("select[name=end_month]").val()); + start_year = parseInt($("select[name=start_year]").val()); + end_year = parseInt($("select[name=end_year]").val()); + av.inflate(true); + }) + } + show_event_clicked = false; + eventsManager(); + var s = start_month, + e = end_month + y = start_year; + e = (e > s && start_year == end_year? e : e + 11); + if(end_year > start_year) + e = e + ((end_year - start_year -1) * 12); + else e--; + for(var i = s;i <= e+1; i++){ + var m = new Month(s,y); + s++; + if(s > 11){ + s = 0; + y++; + } + if(e == 0) + agenda_space.text("Invalid Range of Dates.") + else + agenda_space.append(m.monthDom); + } + + loading(false); + } + + this.hide = function(){ + cache = true; + _calendar.rangeSelection.hide(); + agenda_space.hide(); + _calendar.navigation.show(); + _calendar.calendar.show(); + } + + this.show = function(){ + _calendar.rangeSelection.show(); + agenda_space.show(); + } + var copyObject = function(x){ + return x.clone(); + } + var eventsManager = function(){ + var url = "/xhr/calendars/agenda", + sd = new Date(start_year,start_month,1), + ed = new Date(end_year,end_month+1,0); + $.ajax({ + type : "get", + url : url, + data : {"agenda_start":sd.toLocaleString(),"agenda_end":ed.toLocaleString(),"page_id" : _calendar.page_id}, + success : function(events){ + $.each(events,function(i,e){ + var ed = eventDom(e), + s = new Date(e.start), + e = new Date(e.end), + e_m = ((e.getMonth() > s.getMonth() || s.getMonth() == e.getMonth()) && s.getFullYear() == e.getFullYear() ? e.getMonth() : e.getMonth() + 12) + s_m = s.getMonth(), + s_y = s.getFullYear(); + if(e.getFullYear() > s.getFullYear()) + e_m = e_m + ((e.getFullYear() - s.getFullYear() -1) * 12); + for(var i = s_m; i < e_m + 1; i++){ + var temp_ed = copyObject(ed); + var list = agenda_space.find("div[data-month="+s_m+"][data-year="+s_y+"] table.event_list tbody"); + list.append(temp_ed); + s_m++; + if(s_m > 11){ + s_m = 0; + s_y++; + } + } + if(s.getDate() == e.getDate() && s.getMonth() == s.getMonth() && e.getFullYear() == e.getFullYear()){ + var td = agenda_space.find("td[data-date-node="+s.getDate()+"-"+s.getMonth()+"-"+s.getFullYear()+"]"); + td.addClass("has_event"); + }else{ + var timeDiff = Math.abs(e.getTime() - s.getTime()), + diffDays = Math.ceil(timeDiff / (1000 * 3600 * 24)), + c_m = s.getMonth(), + c_d = s.getDate(), + c_y = s.getFullYear(), + end_of_c_month = new Date(s.getFullYear(),s.getMonth()+1,0).getDate(); + + for(var i = 0; i <= diffDays; i++){ + var td = agenda_space.find("td[data-date-node="+c_d+"-"+c_m+"-"+c_y+"]"); + td.addClass("has_event"); + c_d++; + if(c_d > end_of_c_month){ + c_d = 1; + c_m++; + if(c_m > 11){ + c_m = 0; + c_y++; + } + } + } + } + }) + agenda_space.find("table.event_list tbody").each(function(){ + if($(this).find("tr").length > 1) + $(this).find("td.no_events").parent().remove(); + }) + } + }) + var eventDom = function(event){ + var e_t = $(event_template), + s = new Date(event.start), + e = new Date(event.end), + dateFormat = ""; + if(s.getDate() == e.getDate() && s.getMonth() == s.getMonth() && e.getFullYear() == e.getFullYear()) + dateFormat = $.fullCalendar.formatDate(s, "ddd dd"); + else + dateFormat = $.fullCalendar.formatDates(s, e, "ddd dd, MMM - {ddd dd, MMM}"); + e_t.find("th").text(dateFormat); + e_t.find("td.event_time").text((event.allDay ? "All Day" : $.fullCalendar.formatDate(s, "hh:mm"))); + e_t.find("div.event").text(event.title).css("color",event.color); + return e_t; + } + + } + + var loading = function(bool) { + if (bool) _calendar.loading.css("left",($(window).width()/2 - 60) + "px").show(); + else _calendar.loading.hide(); + } + + var renderHead = function(){ + var head = $(head_template); + var start_month_select = head.find("select[name=start_month]"); + for(var i = 0; i < 12; i++){ + var option = $(""); + if(i == start_month) + option.attr("selected","selected"); + start_month_select.append(option); + } + var end_month_select = head.find("select[name=end_month]"); + for(var i = 0; i < 12; i++){ + var option = $(""); + if(i == end_month) + option.attr("selected","selected"); + end_month_select.append(option); + } + + var start_year_select = head.find("select[name=start_year]"); + var y = start_year - 5; + for(var i = 0; i < 10; i++){ + var option = $(""); + if(y == start_year) + option.attr("selected","selected"); + start_year_select.append(option); + y++; + } + var end_year_select = head.find("select[name=end_year]"); + y = start_year - 5; + for(var i = 0; i < 10; i++){ + var option = $(""); + if(y == end_year) + option.attr("selected","selected"); + end_year_select.append(option); + y++; + } + return head; + } + + var Month = function(month,year){ + _this = this; + this.monthDom = $("
"); + var template = $(month_template); + var list_template = $(event_list_template); + var firstDay = new Date(year,month,1); + var lastDay = new Date(year,month+1,0); + var last_inserted_date = 1; + + var renderMonth = function(){ + var num_of_rows = getNumberOfRows(year,month) + for(var i = 0; i < num_of_rows; i++){ + var tr = null; + if(i == 0) + tr = makeRow("first"); + else if(i == (num_of_rows - 1)){ + tr = makeRow("last"); + }else{ + tr = makeRow("middle"); + } + if(tr == null){ + break; + } + template.find("table.table tbody").append(tr); + template.find("h4").text(monthNames[firstDay.getMonth()] + " - " + firstDay.getFullYear()); + } + _this.monthDom.append(template); + _this.monthDom.append(list_template); + } + + function getNumberOfRows(year, month) { + var day = 1, + sat_counter = 0, + sunday_counter = 0, + date = new Date(year, month, day); + + while(date.getMonth() === month) { + if(date.getDay() === 0) { + sunday_counter++; + }else if(date.getDay() === 6) { + sat_counter++; + } + day++; + date = new Date(year, month, day); + } + return (sunday_counter == 5 && sat_counter == 5 ? 6 : 5); + } + + var makeRow = function(position){ + if(last_inserted_date <= lastDay.getDate()){ + var row = $(""); + switch (position){ + case "first": + for(var i = 0;i < 7;i++){ + var td = $(""); + if(i >= firstDay.getDay()){ + td.text(last_inserted_date); + td.attr("data-date-node",last_inserted_date+"-"+firstDay.getMonth()+"-"+firstDay.getFullYear()); + last_inserted_date++; + } + row.append(td); + } + break; + case "middle": + for(var i = 0;i < 7;i++){ + var td = $(""); + td.text(last_inserted_date); + td.attr("data-date-node",last_inserted_date+"-"+firstDay.getMonth()+"-"+firstDay.getFullYear()); + last_inserted_date++; + row.append(td); + } + break; + case "last": + for(var i = 0;i < 7;i++){ + var td = $(""); + if(i <= lastDay.getDay()){ + td.text(last_inserted_date); + td.attr("data-date-node",last_inserted_date+"-"+firstDay.getMonth()+"-"+firstDay.getFullYear()); + last_inserted_date++; + } + row.append(td); + } + break; + } + }else{ + var row = null; + } + return row; + } + + renderMonth(); + } + +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/javascripts/date.time.picker.js b/app/assets/javascripts/date.time.picker.js new file mode 100644 index 0000000..0308b3e --- /dev/null +++ b/app/assets/javascripts/date.time.picker.js @@ -0,0 +1,17 @@ +!function ($) { + $.fn.datetimepick = function() { + var $this = this, + $data = $this.data(); + $this.datetimepicker({ + format: $data.dateFormat, + pickTime: $data.picktime, + language: $data.language, + }); + } +}(window.jQuery); + +$(function(){ + $('.datetimepick').each(function() { + $(this).datetimepick(); + }); +}); \ No newline at end of file diff --git a/app/assets/javascripts/fullcalendar.js b/app/assets/javascripts/fullcalendar.js new file mode 100644 index 0000000..e514e6e --- /dev/null +++ b/app/assets/javascripts/fullcalendar.js @@ -0,0 +1,5380 @@ +/*! + * FullCalendar v1.6.1 + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + +/* + * Use fullcalendar.css for basic styling. + * For event drag & drop, requires jQuery UI draggable. + * For event resizing, requires jQuery UI resizable. + */ + +(function($, undefined) { + + +;; + +var defaults = { + + // display + defaultView: 'month', + aspectRatio: 1.35, + header: { + left: 'title', + center: '', + right: 'today prev,next' + }, + weekends: true, + weekNumbers: false, + weekNumberCalculation: 'iso', + weekNumberTitle: 'W', + + // editing + //editable: false, + //disableDragging: false, + //disableResizing: false, + + allDayDefault: true, + ignoreTimezone: true, + + // event ajax + lazyFetching: true, + startParam: 'start', + endParam: 'end', + + // time formats + titleFormat: { + month: 'MMMM yyyy', + week: "MMM d[ yyyy]{ '—'[ MMM] d yyyy}", + day: 'dddd, MMM d, yyyy' + }, + columnFormat: { + month: 'ddd', + week: 'ddd M/d', + day: 'dddd M/d' + }, + timeFormat: { // for event elements + '': 'h(:mm)t' // default + }, + + // locale + isRTL: false, + firstDay: 0, + monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'], + monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'], + dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'], + dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'], + buttonText: { + prev: "", + next: "", + prevYear: "«", + nextYear: "»", + today: 'today', + month: 'month', + week: 'week', + day: 'day' + }, + + // jquery-ui theming + theme: false, + buttonIcons: { + prev: 'circle-triangle-w', + next: 'circle-triangle-e' + }, + + //selectable: false, + unselectAuto: true, + + dropAccept: '*' + +}; + +// right-to-left defaults +var rtlDefaults = { + header: { + left: 'next,prev today', + center: '', + right: 'title' + }, + buttonText: { + prev: "", + next: "", + prevYear: "»", + nextYear: "«" + }, + buttonIcons: { + prev: 'circle-triangle-e', + next: 'circle-triangle-w' + } +}; + + + +;; + +var fc = $.fullCalendar = { version: "1.6.1" }; +var fcViews = fc.views = {}; + + +$.fn.fullCalendar = function(options) { + + + // method calling + if (typeof options == 'string') { + var args = Array.prototype.slice.call(arguments, 1); + var res; + this.each(function() { + var calendar = $.data(this, 'fullCalendar'); + if (calendar && $.isFunction(calendar[options])) { + var r = calendar[options].apply(calendar, args); + if (res === undefined) { + res = r; + } + if (options == 'destroy') { + $.removeData(this, 'fullCalendar'); + } + } + }); + if (res !== undefined) { + return res; + } + return this; + } + + + // would like to have this logic in EventManager, but needs to happen before options are recursively extended + var eventSources = options.eventSources || []; + delete options.eventSources; + if (options.events) { + eventSources.push(options.events); + delete options.events; + } + + + options = $.extend(true, {}, + defaults, + (options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {}, + options + ); + + + this.each(function(i, _element) { + var element = $(_element); + var calendar = new Calendar(element, options, eventSources); + element.data('fullCalendar', calendar); // TODO: look into memory leak implications + calendar.render(); + }); + + + return this; + +}; + + +// function for adding/overriding defaults +function setDefaults(d) { + $.extend(true, defaults, d); +} + + + +;; + + +function Calendar(element, options, eventSources) { + var t = this; + + + // exports + t.options = options; + t.render = render; + t.destroy = destroy; + t.refetchEvents = refetchEvents; + t.reportEvents = reportEvents; + t.reportEventChange = reportEventChange; + t.rerenderEvents = rerenderEvents; + t.changeView = changeView; + t.select = select; + t.unselect = unselect; + t.prev = prev; + t.next = next; + t.prevYear = prevYear; + t.nextYear = nextYear; + t.today = today; + t.gotoDate = gotoDate; + t.incrementDate = incrementDate; + t.formatDate = function(format, date) { return formatDate(format, date, options) }; + t.formatDates = function(format, date1, date2) { return formatDates(format, date1, date2, options) }; + t.getDate = getDate; + t.getView = getView; + t.option = option; + t.trigger = trigger; + + + // imports + EventManager.call(t, options, eventSources); + var isFetchNeeded = t.isFetchNeeded; + var fetchEvents = t.fetchEvents; + + + // locals + var _element = element[0]; + var header; + var headerElement; + var content; + var tm; // for making theme classes + var currentView; + var viewInstances = {}; + var elementOuterWidth; + var suggestedViewHeight; + var absoluteViewElement; + var resizeUID = 0; + var ignoreWindowResize = 0; + var date = new Date(); + var events = []; + var _dragElement; + + + + /* Main Rendering + -----------------------------------------------------------------------------*/ + + + setYMD(date, options.year, options.month, options.date); + + + function render(inc) { + if (!content) { + initialRender(); + }else{ + calcSize(); + markSizesDirty(); + markEventsDirty(); + renderView(inc); + } + } + + + function initialRender() { + tm = options.theme ? 'ui' : 'fc'; + element.addClass('fc'); + if (options.isRTL) { + element.addClass('fc-rtl'); + } + else { + element.addClass('fc-ltr'); + } + if (options.theme) { + element.addClass('ui-widget'); + } + content = $("
") + .prependTo(element); + header = new Header(t, options); + headerElement = header.render(); + if (headerElement) { + element.prepend(headerElement); + } + changeView(options.defaultView); + $(window).resize(windowResize); + // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize + if (!bodyVisible()) { + lateRender(); + } + } + + + // called when we know the calendar couldn't be rendered when it was initialized, + // but we think it's ready now + function lateRender() { + setTimeout(function() { // IE7 needs this so dimensions are calculated correctly + if (!currentView.start && bodyVisible()) { // !currentView.start makes sure this never happens more than once + renderView(); + } + },0); + } + + + function destroy() { + $(window).unbind('resize', windowResize); + header.destroy(); + content.remove(); + element.removeClass('fc fc-rtl ui-widget'); + } + + + + function elementVisible() { + return _element.offsetWidth !== 0; + } + + + function bodyVisible() { + return $('body')[0].offsetWidth !== 0; + } + + + + /* View Rendering + -----------------------------------------------------------------------------*/ + + // TODO: improve view switching (still weird transition in IE, and FF has whiteout problem) + + function changeView(newViewName) { + if (!currentView || newViewName != currentView.name) { + ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached + + unselect(); + + var oldView = currentView; + var newViewElement; + + if (oldView) { + (oldView.beforeHide || noop)(); // called before changing min-height. if called after, scroll state is reset (in Opera) + setMinHeight(content, content.height()); + oldView.element.hide(); + }else{ + setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated + } + content.css('overflow', 'hidden'); + + currentView = viewInstances[newViewName]; + if (currentView) { + currentView.element.show(); + }else{ + currentView = viewInstances[newViewName] = new fcViews[newViewName]( + newViewElement = absoluteViewElement = + $("
") + .appendTo(content), + t // the calendar object + ); + } + + if (oldView) { + header.deactivateButton(oldView.name); + } + header.activateButton(newViewName); + + renderView(); // after height has been set, will make absoluteViewElement's position=relative, then set to null + + content.css('overflow', ''); + if (oldView) { + setMinHeight(content, 1); + } + + if (!newViewElement) { + (currentView.afterShow || noop)(); // called after setting min-height/overflow, so in final scroll state (for Opera) + } + + ignoreWindowResize--; + } + } + + + + function renderView(inc) { + if (elementVisible()) { + ignoreWindowResize++; // because renderEvents might temporarily change the height before setSize is reached + + unselect(); + + if (suggestedViewHeight === undefined) { + calcSize(); + } + + var forceEventRender = false; + if (!currentView.start || inc || date < currentView.start || date >= currentView.end) { + // view must render an entire new date range (and refetch/render events) + currentView.render(date, inc || 0); // responsible for clearing events + setSize(true); + forceEventRender = true; + } + else if (currentView.sizeDirty) { + // view must resize (and rerender events) + currentView.clearEvents(); + setSize(); + forceEventRender = true; + } + else if (currentView.eventsDirty) { + currentView.clearEvents(); + forceEventRender = true; + } + currentView.sizeDirty = false; + currentView.eventsDirty = false; + updateEvents(forceEventRender); + + elementOuterWidth = element.outerWidth(); + + header.updateTitle(currentView.title); + var today = new Date(); + if (today >= currentView.start && today < currentView.end) { + header.disableButton('today'); + }else{ + header.enableButton('today'); + } + + ignoreWindowResize--; + currentView.trigger('viewDisplay', _element); + } + } + + + + /* Resizing + -----------------------------------------------------------------------------*/ + + + function updateSize() { + markSizesDirty(); + if (elementVisible()) { + calcSize(); + setSize(); + unselect(); + currentView.clearEvents(); + currentView.renderEvents(events); + currentView.sizeDirty = false; + } + } + + + function markSizesDirty() { + $.each(viewInstances, function(i, inst) { + inst.sizeDirty = true; + }); + } + + + function calcSize() { + if (options.contentHeight) { + suggestedViewHeight = options.contentHeight; + } + else if (options.height) { + suggestedViewHeight = options.height - (headerElement ? headerElement.height() : 0) - vsides(content); + } + else { + suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5)); + } + } + + + function setSize(dateChanged) { // todo: dateChanged? + ignoreWindowResize++; + currentView.setHeight(suggestedViewHeight, dateChanged); + if (absoluteViewElement) { + absoluteViewElement.css('position', 'relative'); + absoluteViewElement = null; + } + currentView.setWidth(content.width(), dateChanged); + ignoreWindowResize--; + } + + + function windowResize() { + if (!ignoreWindowResize) { + if (currentView.start) { // view has already been rendered + var uid = ++resizeUID; + setTimeout(function() { // add a delay + if (uid == resizeUID && !ignoreWindowResize && elementVisible()) { + if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) { + ignoreWindowResize++; // in case the windowResize callback changes the height + updateSize(); + currentView.trigger('windowResize', _element); + ignoreWindowResize--; + } + } + }, 200); + }else{ + // calendar must have been initialized in a 0x0 iframe that has just been resized + lateRender(); + } + } + } + + + + /* Event Fetching/Rendering + -----------------------------------------------------------------------------*/ + + + // fetches events if necessary, rerenders events if necessary (or if forced) + function updateEvents(forceRender) { + if (!options.lazyFetching || isFetchNeeded(currentView.visStart, currentView.visEnd)) { + refetchEvents(); + } + else if (forceRender) { + rerenderEvents(); + } + } + + + function refetchEvents() { + fetchEvents(currentView.visStart, currentView.visEnd); // will call reportEvents + } + + + // called when event data arrives + function reportEvents(_events) { + events = _events; + rerenderEvents(); + } + + + // called when a single event's data has been changed + function reportEventChange(eventID) { + rerenderEvents(eventID); + } + + + // attempts to rerenderEvents + function rerenderEvents(modifiedEventID) { + markEventsDirty(); + if (elementVisible()) { + currentView.clearEvents(); + currentView.renderEvents(events, modifiedEventID); + currentView.eventsDirty = false; + } + } + + + function markEventsDirty() { + $.each(viewInstances, function(i, inst) { + inst.eventsDirty = true; + }); + } + + + + /* Selection + -----------------------------------------------------------------------------*/ + + + function select(start, end, allDay) { + currentView.select(start, end, allDay===undefined ? true : allDay); + } + + + function unselect() { // safe to be called before renderView + if (currentView) { + currentView.unselect(); + } + } + + + + /* Date + -----------------------------------------------------------------------------*/ + + + function prev() { + renderView(-1); + } + + + function next() { + renderView(1); + } + + + function prevYear() { + addYears(date, -1); + renderView(); + } + + + function nextYear() { + addYears(date, 1); + renderView(); + } + + + function today() { + date = new Date(); + renderView(); + } + + + function gotoDate(year, month, dateOfMonth) { + if (year instanceof Date) { + date = cloneDate(year); // provided 1 argument, a Date + }else{ + setYMD(date, year, month, dateOfMonth); + } + renderView(); + } + + + function incrementDate(years, months, days) { + if (years !== undefined) { + addYears(date, years); + } + if (months !== undefined) { + addMonths(date, months); + } + if (days !== undefined) { + addDays(date, days); + } + renderView(); + } + + + function getDate() { + return cloneDate(date); + } + + + + /* Misc + -----------------------------------------------------------------------------*/ + + + function getView() { + return currentView; + } + + + function option(name, value) { + if (value === undefined) { + return options[name]; + } + if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') { + options[name] = value; + updateSize(); + } + } + + + function trigger(name, thisObj) { + if (options[name]) { + return options[name].apply( + thisObj || _element, + Array.prototype.slice.call(arguments, 2) + ); + } + } + + + + /* External Dragging + ------------------------------------------------------------------------*/ + + if (options.droppable) { + $(document) + .bind('dragstart', function(ev, ui) { + var _e = ev.target; + var e = $(_e); + if (!e.parents('.fc').length) { // not already inside a calendar + var accept = options.dropAccept; + if ($.isFunction(accept) ? accept.call(_e, e) : e.is(accept)) { + _dragElement = _e; + currentView.dragStart(_dragElement, ev, ui); + } + } + }) + .bind('dragstop', function(ev, ui) { + if (_dragElement) { + currentView.dragStop(_dragElement, ev, ui); + _dragElement = null; + } + }); + } + + +} + +;; + +function Header(calendar, options) { + var t = this; + + + // exports + t.render = render; + t.destroy = destroy; + t.updateTitle = updateTitle; + t.activateButton = activateButton; + t.deactivateButton = deactivateButton; + t.disableButton = disableButton; + t.enableButton = enableButton; + + + // locals + var element = $([]); + var tm; + + + + function render() { + tm = options.theme ? 'ui' : 'fc'; + var sections = options.header; + if (sections) { + element = $("") + .append( + $("") + .append(renderSection('left')) + .append(renderSection('center')) + .append(renderSection('right')) + ); + return element; + } + } + + + function destroy() { + element.remove(); + } + + + function renderSection(position) { + var e = $("" + + ""; + + if (showWeekNumbers) { + s += ""; + } + + for (i=0; i"; // fc- needed for setDayID + } + s += + "" + + "" + + "" + + "" + + "" + + ""; + for (i=0; i" + // fc- needed for setDayID + "
" + + "
" + + "
 
" + + "
" + + "
" + + ""; + } + s += + "
" + + "" + + "" + + "
"); + var buttonStr = options.header[position]; + if (buttonStr) { + $.each(buttonStr.split(' '), function(i) { + if (i > 0) { + e.append(""); + } + var prevButton; + $.each(this.split(','), function(j, buttonName) { + console.log(buttonName); + if (buttonName == 'title') { + e.append("

 

"); + if (prevButton) { + prevButton.addClass(tm + '-corner-right'); + } + prevButton = null; + }else{ + var buttonClick; + if (calendar[buttonName]) { + buttonClick = calendar[buttonName]; // calendar method + } + else if (fcViews[buttonName]) { + buttonClick = function() { + button.removeClass(tm + '-state-hover'); // forget why + calendar.changeView(buttonName); + }; + } + if (buttonClick) { + var icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null; // why are we using smartProperty here? + var text = smartProperty(options.buttonText, buttonName); // why are we using smartProperty here? + var button = $( + "" + + (icon ? + "" + + "" + + "" : + text + ) + + "" + ) + .click(function() { + if (!button.hasClass(tm + '-state-disabled')) { + buttonClick(); + } + }) + .mousedown(function() { + button + .not('.' + tm + '-state-active') + .not('.' + tm + '-state-disabled') + .addClass(tm + '-state-down'); + }) + .mouseup(function() { + button.removeClass(tm + '-state-down'); + }) + .hover( + function() { + button + .not('.' + tm + '-state-active') + .not('.' + tm + '-state-disabled') + .addClass(tm + '-state-hover'); + }, + function() { + button + .removeClass(tm + '-state-hover') + .removeClass(tm + '-state-down'); + } + ) + .appendTo(e); + disableTextSelection(button); + if (!prevButton) { + button.addClass(tm + '-corner-left'); + } + prevButton = button; + } + } + }); + if (prevButton) { + prevButton.addClass(tm + '-corner-right'); + } + }); + } + return e; + } + + + function updateTitle(html) { + element.find('h2') + .html(html); + } + + + function activateButton(buttonName) { + element.find('span.fc-button-' + buttonName) + .addClass(tm + '-state-active'); + } + + + function deactivateButton(buttonName) { + element.find('span.fc-button-' + buttonName) + .removeClass(tm + '-state-active'); + } + + + function disableButton(buttonName) { + element.find('span.fc-button-' + buttonName) + .addClass(tm + '-state-disabled'); + } + + + function enableButton(buttonName) { + element.find('span.fc-button-' + buttonName) + .removeClass(tm + '-state-disabled'); + } + + +} + +;; + +fc.sourceNormalizers = []; +fc.sourceFetchers = []; + +var ajaxDefaults = { + dataType: 'json', + cache: false +}; + +var eventGUID = 1; + + +function EventManager(options, _sources) { + var t = this; + + + // exports + t.isFetchNeeded = isFetchNeeded; + t.fetchEvents = fetchEvents; + t.addEventSource = addEventSource; + t.removeEventSource = removeEventSource; + t.updateEvent = updateEvent; + t.renderEvent = renderEvent; + t.removeEvents = removeEvents; + t.clientEvents = clientEvents; + t.normalizeEvent = normalizeEvent; + + + // imports + var trigger = t.trigger; + var getView = t.getView; + var reportEvents = t.reportEvents; + + + // locals + var stickySource = { events: [] }; + var sources = [ stickySource ]; + var rangeStart, rangeEnd; + var currentFetchID = 0; + var pendingSourceCnt = 0; + var loadingLevel = 0; + var cache = []; + + + for (var i=0; i<_sources.length; i++) { + _addEventSource(_sources[i]); + } + + + + /* Fetching + -----------------------------------------------------------------------------*/ + + + function isFetchNeeded(start, end) { + return !rangeStart || start < rangeStart || end > rangeEnd; + } + + + function fetchEvents(start, end) { + rangeStart = start; + rangeEnd = end; + cache = []; + var fetchID = ++currentFetchID; + var len = sources.length; + pendingSourceCnt = len; + for (var i=0; i)), return null instead + return null; +} + + +function parseISO8601(s, ignoreTimezone) { // ignoreTimezone defaults to false + // derived from http://delete.me.uk/2005/03/iso8601.html + // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html + var m = s.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/); + if (!m) { + return null; + } + var date = new Date(m[1], 0, 1); + if (ignoreTimezone || !m[13]) { + var check = new Date(m[1], 0, 1, 9, 0); + if (m[3]) { + date.setMonth(m[3] - 1); + check.setMonth(m[3] - 1); + } + if (m[5]) { + date.setDate(m[5]); + check.setDate(m[5]); + } + fixDate(date, check); + if (m[7]) { + date.setHours(m[7]); + } + if (m[8]) { + date.setMinutes(m[8]); + } + if (m[10]) { + date.setSeconds(m[10]); + } + if (m[12]) { + date.setMilliseconds(Number("0." + m[12]) * 1000); + } + fixDate(date, check); + }else{ + date.setUTCFullYear( + m[1], + m[3] ? m[3] - 1 : 0, + m[5] || 1 + ); + date.setUTCHours( + m[7] || 0, + m[8] || 0, + m[10] || 0, + m[12] ? Number("0." + m[12]) * 1000 : 0 + ); + if (m[14]) { + var offset = Number(m[16]) * 60 + (m[18] ? Number(m[18]) : 0); + offset *= m[15] == '-' ? 1 : -1; + date = new Date(+date + (offset * 60 * 1000)); + } + } + return date; +} + + +function parseTime(s) { // returns minutes since start of day + if (typeof s == 'number') { // an hour + return s * 60; + } + if (typeof s == 'object') { // a Date object + return s.getHours() * 60 + s.getMinutes(); + } + var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/); + if (m) { + var h = parseInt(m[1], 10); + if (m[3]) { + h %= 12; + if (m[3].toLowerCase().charAt(0) == 'p') { + h += 12; + } + } + return h * 60 + (m[2] ? parseInt(m[2], 10) : 0); + } +} + + + +/* Date Formatting +-----------------------------------------------------------------------------*/ +// TODO: use same function formatDate(date, [date2], format, [options]) + + +function formatDate(date, format, options) { + return formatDates(date, null, format, options); +} + + +function formatDates(date1, date2, format, options) { + options = options || defaults; + var date = date1, + otherDate = date2, + i, len = format.length, c, + i2, formatter, + res = ''; + for (i=0; ii; i2--) { + if (formatter = dateFormatters[format.substring(i, i2)]) { + if (date) { + res += formatter(date, options); + } + i = i2 - 1; + break; + } + } + if (i2 == i) { + if (date) { + res += c; + } + } + } + } + return res; +}; + + +var dateFormatters = { + s : function(d) { return d.getSeconds() }, + ss : function(d) { return zeroPad(d.getSeconds()) }, + m : function(d) { return d.getMinutes() }, + mm : function(d) { return zeroPad(d.getMinutes()) }, + h : function(d) { return d.getHours() % 12 || 12 }, + hh : function(d) { return zeroPad(d.getHours() % 12 || 12) }, + H : function(d) { return d.getHours() }, + HH : function(d) { return zeroPad(d.getHours()) }, + d : function(d) { return d.getDate() }, + dd : function(d) { return zeroPad(d.getDate()) }, + ddd : function(d,o) { return o.dayNamesShort[d.getDay()] }, + dddd: function(d,o) { return o.dayNames[d.getDay()] }, + M : function(d) { return d.getMonth() + 1 }, + MM : function(d) { return zeroPad(d.getMonth() + 1) }, + MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] }, + MMMM: function(d,o) { return o.monthNames[d.getMonth()] }, + yy : function(d) { return (d.getFullYear()+'').substring(2) }, + yyyy: function(d) { return d.getFullYear() }, + t : function(d) { return d.getHours() < 12 ? 'a' : 'p' }, + tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' }, + T : function(d) { return d.getHours() < 12 ? 'A' : 'P' }, + TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' }, + u : function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") }, + S : function(d) { + var date = d.getDate(); + if (date > 10 && date < 20) { + return 'th'; + } + return ['st', 'nd', 'rd'][date%10-1] || 'th'; + }, + w : function(d, o) { // local + return o.weekNumberCalculation(d); + }, + W : function(d) { // ISO + return iso8601Week(d); + } +}; +fc.dateFormatters = dateFormatters; + + +/* thanks jQuery UI (https://github.com/jquery/jquery-ui/blob/master/ui/jquery.ui.datepicker.js) + * + * Set as calculateWeek to determine the week of the year based on the ISO 8601 definition. + * @param date Date - the date to get the week for + * @return number - the number of the week within the year that contains this date + */ +function iso8601Week(date) { + var time; + var checkDate = new Date(date.getTime()); + + // Find Thursday of this week starting on Monday + checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); + + time = checkDate.getTime(); + checkDate.setMonth(0); // Compare with Jan 1 + checkDate.setDate(1); + return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; +} + + +;; + +fc.applyAll = applyAll; + + +/* Event Date Math +-----------------------------------------------------------------------------*/ + + +function exclEndDay(event) { + if (event.end) { + return _exclEndDay(event.end, event.allDay); + }else{ + return addDays(cloneDate(event.start), 1); + } +} + + +function _exclEndDay(end, allDay) { + end = cloneDate(end); + return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : clearTime(end); +} + + +function segCmp(a, b) { + return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start); +} + + +function segsCollide(seg1, seg2) { + return seg1.end > seg2.start && seg1.start < seg2.end; +} + + + +/* Event Sorting +-----------------------------------------------------------------------------*/ + + +// event rendering utilities +function sliceSegs(events, visEventEnds, start, end) { + var segs = [], + i, len=events.length, event, + eventStart, eventEnd, + segStart, segEnd, + isStart, isEnd; + for (i=0; i start && eventStart < end) { + if (eventStart < start) { + segStart = cloneDate(start); + isStart = false; + }else{ + segStart = eventStart; + isStart = true; + } + if (eventEnd > end) { + segEnd = cloneDate(end); + isEnd = false; + }else{ + segEnd = eventEnd; + isEnd = true; + } + segs.push({ + event: event, + start: segStart, + end: segEnd, + isStart: isStart, + isEnd: isEnd, + msLength: segEnd - segStart + }); + } + } + return segs.sort(segCmp); +} + + +// event rendering calculation utilities +function stackSegs(segs) { + var levels = [], + i, len = segs.length, seg, + j, collide, k; + for (i=0; i=0; i--) { + res = obj[parts[i].toLowerCase()]; + if (res !== undefined) { + return res; + } + } + return obj['']; +} + + +function htmlEscape(s) { + return s.replace(/&/g, '&') + .replace(//g, '>') + .replace(/'/g, ''') + .replace(/"/g, '"') + .replace(/\n/g, '
'); +} + + +function cssKey(_element) { + return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, ''); +} + + +function disableTextSelection(element) { + element + .attr('unselectable', 'on') + .css('MozUserSelect', 'none') + .bind('selectstart.ui', function() { return false; }); +} + + +/* +function enableTextSelection(element) { + element + .attr('unselectable', 'off') + .css('MozUserSelect', '') + .unbind('selectstart.ui'); +} +*/ + + +function markFirstLast(e) { + e.children() + .removeClass('fc-first fc-last') + .filter(':first-child') + .addClass('fc-first') + .end() + .filter(':last-child') + .addClass('fc-last'); +} + + +function setDayID(cell, date) { + cell.each(function(i, _cell) { + _cell.className = _cell.className.replace(/^fc-\w*/, 'fc-' + dayIDs[date.getDay()]); + // TODO: make a way that doesn't rely on order of classes + }); +} + + +function getSkinCss(event, opt) { + var source = event.source || {}; + var eventColor = event.color; + var sourceColor = source.color; + var optionColor = opt('eventColor'); + var backgroundColor = + event.backgroundColor || + eventColor || + source.backgroundColor || + sourceColor || + opt('eventBackgroundColor') || + optionColor || + event.color; + var borderColor = + event.borderColor || + eventColor || + source.borderColor || + sourceColor || + opt('eventBorderColor') || + optionColor; + var textColor = + event.textColor || + source.textColor || + opt('eventTextColor'); + var statements = []; + if (backgroundColor) { + statements.push('background-color:' + backgroundColor); + } + if (borderColor) { + statements.push('border-color:' + borderColor); + } + if (textColor) { + statements.push('color:' + textColor); + } + return statements.join(';'); +} + + +function applyAll(functions, thisObj, args) { + if ($.isFunction(functions)) { + functions = [ functions ]; + } + if (functions) { + var i; + var ret; + for (i=0; i") + .appendTo(element); + } + + + + function buildTable(showNumbers) { + var html = ''; + var i, j; + var headerClass = tm + "-widget-header"; + var contentClass = tm + "-widget-content"; + var month = t.start.getMonth(); + var today = clearTime(new Date()); + var cellDate; // not to be confused with local function. TODO: better names + var cellClasses; + var cell; + + html += "" + + "" + + ""; + + if (showWeekNumbers) { + html += "" + + "" + + ""; + + for (i=0; i" + + "
" + + ""; + } + + for (j=0; j" + + "
"; + if (showNumbers) { + html += "
" + cellDate.getDate() + "
"; + } + html += "
" + + "
 
" + + "
" + + "
" + + ""; + } + + html += ""; + } + html += "
" + + "
"; + } + + for (i=0; i"; + } + + html += "
"; + + lockHeight(); // the unlock happens later, in setHeight()... + if (table) { + table.remove(); + } + table = $(html).appendTo(element); + + head = table.find('thead'); + headCells = head.find('.fc-day-header'); + body = table.find('tbody'); + bodyRows = body.find('tr'); + bodyCells = body.find('.fc-day'); + bodyFirstCells = bodyRows.find('td:first-child'); + bodyCellTopInners = bodyRows.eq(0).find('.fc-day-content > div'); + + markFirstLast(head.add(head.find('tr'))); // marks first+last tr/th's + markFirstLast(bodyRows); // marks first+last td's + bodyRows.eq(0).addClass('fc-first'); + bodyRows.filter(':last').addClass('fc-last'); + + if (showWeekNumbers) { + head.find('.fc-week-number').text(weekNumberTitle); + } + + headCells.each(function(i, _cell) { + var date = indexDate(i); + $(_cell).text(formatDate(date, colFormat)); + }); + + if (showWeekNumbers) { + body.find('.fc-week-number > div').each(function(i, _cell) { + var weekStart = _cellDate(i, 0); + $(_cell).text(formatDate(weekStart, weekNumberFormat)); + }); + } + + bodyCells.each(function(i, _cell) { + var date = indexDate(i); + trigger('dayRender', t, date, $(_cell)); + }); + + dayBind(bodyCells); + } + + + + function setHeight(height) { + viewHeight = height; + + var bodyHeight = viewHeight - head.height(); + var rowHeight; + var rowHeightLast; + var cell; + + if (opt('weekMode') == 'variable') { + rowHeight = rowHeightLast = Math.floor(bodyHeight / (rowCnt==1 ? 2 : 6)); + }else{ + rowHeight = Math.floor(bodyHeight / rowCnt); + rowHeightLast = bodyHeight - rowHeight * (rowCnt-1); + } + + bodyFirstCells.each(function(i, _cell) { + if (i < rowCnt) { + cell = $(_cell); + setMinHeight( + cell.find('> div'), + (i==rowCnt-1 ? rowHeightLast : rowHeight) - vsides(cell) + ); + } + }); + + unlockHeight(); + } + + + function setWidth(width) { + viewWidth = width; + colContentPositions.clear(); + + weekNumberWidth = 0; + if (showWeekNumbers) { + weekNumberWidth = head.find('th.fc-week-number').outerWidth(); + } + + colWidth = Math.floor((viewWidth - weekNumberWidth) / colCnt); + setOuterWidth(headCells.slice(0, -1), colWidth); + } + + + + /* Day clicking and binding + -----------------------------------------------------------*/ + + + function dayBind(days) { + days.click(dayClick) + .mousedown(daySelectionMousedown); + } + + + function dayClick(ev) { + if (!opt('selectable')) { // if selectable, SelectionManager will worry about dayClick + var date = parseISO8601($(this).data('date')); + trigger('dayClick', this, date, true, ev); + } + } + + + + /* Semi-transparent Overlay Helpers + ------------------------------------------------------*/ + + + function renderDayOverlay(overlayStart, overlayEnd, refreshCoordinateGrid) { // overlayEnd is exclusive + if (refreshCoordinateGrid) { + coordinateGrid.build(); + } + var rowStart = cloneDate(t.visStart); + var rowEnd = addDays(cloneDate(rowStart), colCnt); + for (var i=0; i" + + "
"; + } + else { + s += "  
  
"; + dayTable = $(s).appendTo(element); + dayHead = dayTable.find('thead'); + dayHeadCells = dayHead.find('th').slice(1, -1); + dayBody = dayTable.find('tbody'); + dayBodyCells = dayBody.find('td').slice(0, -1); + dayBodyCellInners = dayBodyCells.find('div.fc-day-content div'); + dayBodyFirstCell = dayBodyCells.eq(0); + dayBodyFirstCellStretcher = dayBodyFirstCell.find('> div'); + + markFirstLast(dayHead.add(dayHead.find('tr'))); + markFirstLast(dayBody.add(dayBody.find('tr'))); + + axisFirstCells = dayHead.find('th:first'); + gutterCells = dayTable.find('.fc-agenda-gutter'); + + slotLayer = + $("
") + .appendTo(element); + + if (opt('allDaySlot')) { + + daySegmentContainer = + $("
") + .appendTo(slotLayer); + + s = + "" + + "" + + "" + + "" + + "" + + "" + + "
" + opt('allDayText') + "" + + "
" + + "
 
"; + allDayTable = $(s).appendTo(slotLayer); + allDayRow = allDayTable.find('tr'); + + dayBind(allDayRow.find('td')); + + axisFirstCells = axisFirstCells.add(allDayTable.find('th:first')); + gutterCells = gutterCells.add(allDayTable.find('th.fc-agenda-gutter')); + + slotLayer.append( + "
" + + "
" + + "
" + ); + + }else{ + + daySegmentContainer = $([]); // in jQuery 1.4, we can just do $() + + } + + slotScroller = + $("
") + .appendTo(slotLayer); + + slotContent = + $("
") + .appendTo(slotScroller); + + slotSegmentContainer = + $("
") + .appendTo(slotContent); + + s = + "" + + ""; + d = zeroDate(); + maxd = addMinutes(cloneDate(d), maxMinute); + addMinutes(d, minMinute); + slotCnt = 0; + for (i=0; d < maxd; i++) { + minutes = d.getMinutes(); + s += + "" + + "" + + "" + + ""; + addMinutes(d, opt('slotMinutes')); + slotCnt++; + } + s += + "" + + "
" + + ((!slotNormal || !minutes) ? formatDate(d, opt('axisFormat')) : ' ') + + "" + + "
 
" + + "
"; + slotTable = $(s).appendTo(slotContent); + slotTableFirstInner = slotTable.find('div:first'); + + slotBind(slotTable.find('td')); + + axisFirstCells = axisFirstCells.add(slotTable.find('th:first')); + } + + + + function updateCells() { + var i; + var headCell; + var bodyCell; + var date; + var today = clearTime(new Date()); + + if (showWeekNumbers) { + var weekText = formatDate(colDate(0), weekNumberFormat); + if (rtl) { + weekText = weekText + weekNumberTitle; + } + else { + weekText = weekNumberTitle + weekText; + } + dayHead.find('.fc-week-number').text(weekText); + } + + for (i=0; i= 0) { + addMinutes(d, minMinute + slotIndex * snapMinutes); + } + return d; + } + + + function colDate(col) { // returns dates with 00:00:00 + return addDays(cloneDate(t.visStart), col*dis+dit); + } + + + function cellIsAllDay(cell) { + return opt('allDaySlot') && !cell.row; + } + + + function dayOfWeekCol(dayOfWeek) { + return ((dayOfWeek - Math.max(firstDay, nwe) + colCnt) % colCnt)*dis+dit; + } + + + + + // get the Y coordinate of the given time on the given day (both Date objects) + function timePosition(day, time) { // both date objects. day holds 00:00 of current day + day = cloneDate(day, true); + if (time < addMinutes(cloneDate(day), minMinute)) { + return 0; + } + if (time >= addMinutes(cloneDate(day), maxMinute)) { + return slotTable.height(); + } + var slotMinutes = opt('slotMinutes'), + minutes = time.getHours()*60 + time.getMinutes() - minMinute, + slotI = Math.floor(minutes / slotMinutes), + slotTop = slotTopCache[slotI]; + if (slotTop === undefined) { + slotTop = slotTopCache[slotI] = slotTable.find('tr:eq(' + slotI + ') td div')[0].offsetTop; //.position().top; // need this optimization??? + } + return Math.max(0, Math.round( + slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes) + )); + } + + + function allDayBounds() { + return { + left: axisWidth, + right: viewWidth - gutterWidth + } + } + + + function getAllDayRow(index) { + return allDayRow; + } + + + function defaultEventEnd(event) { + var start = cloneDate(event.start); + if (event.allDay) { + return start; + } + return addMinutes(start, opt('defaultEventMinutes')); + } + + + + /* Selection + ---------------------------------------------------------------------------------*/ + + + function defaultSelectionEnd(startDate, allDay) { + if (allDay) { + return cloneDate(startDate); + } + return addMinutes(cloneDate(startDate), opt('slotMinutes')); + } + + + function renderSelection(startDate, endDate, allDay) { // only for all-day + if (allDay) { + if (opt('allDaySlot')) { + renderDayOverlay(startDate, addDays(cloneDate(endDate), 1), true); + } + }else{ + renderSlotSelection(startDate, endDate); + } + } + + + function renderSlotSelection(startDate, endDate) { + var helperOption = opt('selectHelper'); + coordinateGrid.build(); + if (helperOption) { + var col = dayDiff(startDate, t.visStart) * dis + dit; + if (col >= 0 && col < colCnt) { // only works when times are on same day + var rect = coordinateGrid.rect(0, col, 0, col, slotContent); // only for horizontal coords + var top = timePosition(startDate, startDate); + var bottom = timePosition(startDate, endDate); + if (bottom > top) { // protect against selections that are entirely before or after visible range + rect.top = top; + rect.height = bottom - top; + rect.left += 2; + rect.width -= 5; + if ($.isFunction(helperOption)) { + var helperRes = helperOption(startDate, endDate); + if (helperRes) { + rect.position = 'absolute'; + rect.zIndex = 8; + selectionHelper = $(helperRes) + .css(rect) + .appendTo(slotContent); + } + }else{ + rect.isStart = true; // conside rect a "seg" now + rect.isEnd = true; // + selectionHelper = $(slotSegHtml( + { + title: '', + start: startDate, + end: endDate, + className: ['fc-select-helper'], + editable: false + }, + rect + )); + selectionHelper.css('opacity', opt('dragOpacity')); + } + if (selectionHelper) { + slotBind(selectionHelper); + slotContent.append(selectionHelper); + setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended + setOuterHeight(selectionHelper, rect.height, true); + } + } + } + }else{ + renderSlotOverlay(startDate, endDate); + } + } + + + function clearSelection() { + clearOverlays(); + if (selectionHelper) { + selectionHelper.remove(); + selectionHelper = null; + } + } + + + function slotSelectionMousedown(ev) { + if (ev.which == 1 && opt('selectable')) { // ev.which==1 means left mouse button + unselect(ev); + var dates; + hoverListener.start(function(cell, origCell) { + clearSelection(); + if (cell && cell.col == origCell.col && !cellIsAllDay(cell)) { + var d1 = cellDate(origCell); + var d2 = cellDate(cell); + dates = [ + d1, + addMinutes(cloneDate(d1), snapMinutes), // calculate minutes depending on selection slot minutes + d2, + addMinutes(cloneDate(d2), snapMinutes) + ].sort(cmp); + renderSlotSelection(dates[0], dates[3]); + }else{ + dates = null; + } + }, ev); + $(document).one('mouseup', function(ev) { + hoverListener.stop(); + if (dates) { + if (+dates[0] == +dates[1]) { + reportDayClick(dates[0], false, ev); + } + reportSelection(dates[0], dates[3], false, ev); + } + }); + } + } + + + function reportDayClick(date, allDay, ev) { + trigger('dayClick', dayBodyCells[dayOfWeekCol(date.getDay())], date, allDay, ev); + } + + + + /* External Dragging + --------------------------------------------------------------------------------*/ + + + function dragStart(_dragElement, ev, ui) { + hoverListener.start(function(cell) { + clearOverlays(); + if (cell) { + if (cellIsAllDay(cell)) { + renderCellOverlay(cell.row, cell.col, cell.row, cell.col); + }else{ + var d1 = cellDate(cell); + var d2 = addMinutes(cloneDate(d1), opt('defaultEventMinutes')); + renderSlotOverlay(d1, d2); + } + } + }, ev); + } + + + function dragStop(_dragElement, ev, ui) { + var cell = hoverListener.stop(); + clearOverlays(); + if (cell) { + trigger('drop', _dragElement, cellDate(cell), cellIsAllDay(cell), ev, ui); + } + } + + +} + +;; + +function AgendaEventRenderer() { + var t = this; + + + // exports + t.renderEvents = renderEvents; + t.compileDaySegs = compileDaySegs; // for DayEventRenderer + t.clearEvents = clearEvents; + t.slotSegHtml = slotSegHtml; + t.bindDaySeg = bindDaySeg; + + + // imports + DayEventRenderer.call(t); + var opt = t.opt; + var trigger = t.trigger; + //var setOverflowHidden = t.setOverflowHidden; + var isEventDraggable = t.isEventDraggable; + var isEventResizable = t.isEventResizable; + var eventEnd = t.eventEnd; + var reportEvents = t.reportEvents; + var reportEventClear = t.reportEventClear; + var eventElementHandlers = t.eventElementHandlers; + var setHeight = t.setHeight; + var getDaySegmentContainer = t.getDaySegmentContainer; + var getSlotSegmentContainer = t.getSlotSegmentContainer; + var getHoverListener = t.getHoverListener; + var getMaxMinute = t.getMaxMinute; + var getMinMinute = t.getMinMinute; + var timePosition = t.timePosition; + var colContentLeft = t.colContentLeft; + var colContentRight = t.colContentRight; + var renderDaySegs = t.renderDaySegs; + var resizableDayEvent = t.resizableDayEvent; // TODO: streamline binding architecture + var getColCnt = t.getColCnt; + var getColWidth = t.getColWidth; + var getSnapHeight = t.getSnapHeight; + var getSnapMinutes = t.getSnapMinutes; + var getBodyContent = t.getBodyContent; + var reportEventElement = t.reportEventElement; + var showEvents = t.showEvents; + var hideEvents = t.hideEvents; + var eventDrop = t.eventDrop; + var eventResize = t.eventResize; + var renderDayOverlay = t.renderDayOverlay; + var clearOverlays = t.clearOverlays; + var calendar = t.calendar; + var formatDate = calendar.formatDate; + var formatDates = calendar.formatDates; + + + + /* Rendering + ----------------------------------------------------------------------------*/ + + + function renderEvents(events, modifiedEventId) { + reportEvents(events); + var i, len=events.length, + dayEvents=[], + slotEvents=[]; + for (i=0; i" + + "
" + + "
" + + htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) + + "
" + + "
" + + htmlEscape(event.title) + + "
" + + "
" + + "
"; + if (seg.isEnd && isEventResizable(event)) { + html += + "
=
"; + } + html += + ""; + return html; + } + + + function bindDaySeg(event, eventElement, seg) { + if (isEventDraggable(event)) { + draggableDayEvent(event, eventElement, seg.isStart); + } + if (seg.isEnd && isEventResizable(event)) { + resizableDayEvent(event, eventElement, seg); + } + eventElementHandlers(event, eventElement); + // needs to be after, because resizableDayEvent might stopImmediatePropagation on click + } + + + function bindSlotSeg(event, eventElement, seg) { + var timeElement = eventElement.find('div.fc-event-time'); + if (isEventDraggable(event)) { + draggableSlotEvent(event, eventElement, timeElement); + } + if (seg.isEnd && isEventResizable(event)) { + resizableSlotEvent(event, eventElement, timeElement); + } + eventElementHandlers(event, eventElement); + } + + + + /* Dragging + -----------------------------------------------------------------------------------*/ + + + // when event starts out FULL-DAY + + function draggableDayEvent(event, eventElement, isStart) { + var origWidth; + var revert; + var allDay=true; + var dayDelta; + var dis = opt('isRTL') ? -1 : 1; + var hoverListener = getHoverListener(); + var colWidth = getColWidth(); + var snapHeight = getSnapHeight(); + var snapMinutes = getSnapMinutes(); + var minMinute = getMinMinute(); + eventElement.draggable({ + zIndex: 9, + opacity: opt('dragOpacity', 'month'), // use whatever the month view was using + revertDuration: opt('dragRevertDuration'), + start: function(ev, ui) { + trigger('eventDragStart', eventElement, event, ev, ui); + hideEvents(event, eventElement); + origWidth = eventElement.width(); + hoverListener.start(function(cell, origCell, rowDelta, colDelta) { + clearOverlays(); + if (cell) { + //setOverflowHidden(true); + revert = false; + dayDelta = colDelta * dis; + if (!cell.row) { + // on full-days + renderDayOverlay( + addDays(cloneDate(event.start), dayDelta), + addDays(exclEndDay(event), dayDelta) + ); + resetElement(); + }else{ + // mouse is over bottom slots + if (isStart) { + if (allDay) { + // convert event to temporary slot-event + eventElement.width(colWidth - 10); // don't use entire width + setOuterHeight( + eventElement, + snapHeight * Math.round( + (event.end ? ((event.end - event.start) / MINUTE_MS) : opt('defaultEventMinutes')) / + snapMinutes + ) + ); + eventElement.draggable('option', 'grid', [colWidth, 1]); + allDay = false; + } + }else{ + revert = true; + } + } + revert = revert || (allDay && !dayDelta); + }else{ + resetElement(); + //setOverflowHidden(false); + revert = true; + } + eventElement.draggable('option', 'revert', revert); + }, ev, 'drag'); + }, + stop: function(ev, ui) { + hoverListener.stop(); + clearOverlays(); + trigger('eventDragStop', eventElement, event, ev, ui); + if (revert) { + // hasn't moved or is out of bounds (draggable has already reverted) + resetElement(); + eventElement.css('filter', ''); // clear IE opacity side-effects + showEvents(event, eventElement); + }else{ + // changed! + var minuteDelta = 0; + if (!allDay) { + minuteDelta = Math.round((eventElement.offset().top - getBodyContent().offset().top) / snapHeight) + * snapMinutes + + minMinute + - (event.start.getHours() * 60 + event.start.getMinutes()); + } + eventDrop(this, event, dayDelta, minuteDelta, allDay, ev, ui); + } + //setOverflowHidden(false); + } + }); + function resetElement() { + if (!allDay) { + eventElement + .width(origWidth) + .height('') + .draggable('option', 'grid', null); + allDay = true; + } + } + } + + + // when event starts out IN TIMESLOTS + + function draggableSlotEvent(event, eventElement, timeElement) { + var origPosition; + var allDay=false; + var dayDelta; + var minuteDelta; + var prevMinuteDelta; + var dis = opt('isRTL') ? -1 : 1; + var hoverListener = getHoverListener(); + var colCnt = getColCnt(); + var colWidth = getColWidth(); + var snapHeight = getSnapHeight(); + var snapMinutes = getSnapMinutes(); + eventElement.draggable({ + zIndex: 9, + scroll: false, + grid: [colWidth, snapHeight], + axis: colCnt==1 ? 'y' : false, + opacity: opt('dragOpacity'), + revertDuration: opt('dragRevertDuration'), + start: function(ev, ui) { + trigger('eventDragStart', eventElement, event, ev, ui); + hideEvents(event, eventElement); + origPosition = eventElement.position(); + minuteDelta = prevMinuteDelta = 0; + hoverListener.start(function(cell, origCell, rowDelta, colDelta) { + eventElement.draggable('option', 'revert', !cell); + clearOverlays(); + if (cell) { + dayDelta = colDelta * dis; + if (opt('allDaySlot') && !cell.row) { + // over full days + if (!allDay) { + // convert to temporary all-day event + allDay = true; + timeElement.hide(); + eventElement.draggable('option', 'grid', null); + } + renderDayOverlay( + addDays(cloneDate(event.start), dayDelta), + addDays(exclEndDay(event), dayDelta) + ); + }else{ + // on slots + resetElement(); + } + } + }, ev, 'drag'); + }, + drag: function(ev, ui) { + minuteDelta = Math.round((ui.position.top - origPosition.top) / snapHeight) * snapMinutes; + if (minuteDelta != prevMinuteDelta) { + if (!allDay) { + updateTimeText(minuteDelta); + } + prevMinuteDelta = minuteDelta; + } + }, + stop: function(ev, ui) { + var cell = hoverListener.stop(); + clearOverlays(); + trigger('eventDragStop', eventElement, event, ev, ui); + if (cell && (dayDelta || minuteDelta || allDay)) { + // changed! + eventDrop(this, event, dayDelta, allDay ? 0 : minuteDelta, allDay, ev, ui); + }else{ + // either no change or out-of-bounds (draggable has already reverted) + resetElement(); + eventElement.css('filter', ''); // clear IE opacity side-effects + eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position + updateTimeText(0); + showEvents(event, eventElement); + } + } + }); + function updateTimeText(minuteDelta) { + var newStart = addMinutes(cloneDate(event.start), minuteDelta); + var newEnd; + if (event.end) { + newEnd = addMinutes(cloneDate(event.end), minuteDelta); + } + timeElement.text(formatDates(newStart, newEnd, opt('timeFormat'))); + } + function resetElement() { + // convert back to original slot-event + if (allDay) { + timeElement.css('display', ''); // show() was causing display=inline + eventElement.draggable('option', 'grid', [colWidth, snapHeight]); + allDay = false; + } + } + } + + + + /* Resizing + --------------------------------------------------------------------------------------*/ + + + function resizableSlotEvent(event, eventElement, timeElement) { + var snapDelta, prevSnapDelta; + var snapHeight = getSnapHeight(); + var snapMinutes = getSnapMinutes(); + eventElement.resizable({ + handles: { + s: '.ui-resizable-handle' + }, + grid: snapHeight, + start: function(ev, ui) { + snapDelta = prevSnapDelta = 0; + hideEvents(event, eventElement); + eventElement.css('z-index', 9); + trigger('eventResizeStart', this, event, ev, ui); + }, + resize: function(ev, ui) { + // don't rely on ui.size.height, doesn't take grid into account + snapDelta = Math.round((Math.max(snapHeight, eventElement.height()) - ui.originalSize.height) / snapHeight); + if (snapDelta != prevSnapDelta) { + timeElement.text( + formatDates( + event.start, + (!snapDelta && !event.end) ? null : // no change, so don't display time range + addMinutes(eventEnd(event), snapMinutes*snapDelta), + opt('timeFormat') + ) + ); + prevSnapDelta = snapDelta; + } + }, + stop: function(ev, ui) { + trigger('eventResizeStop', this, event, ev, ui); + if (snapDelta) { + eventResize(this, event, 0, snapMinutes*snapDelta, ev, ui); + }else{ + eventElement.css('z-index', 8); + showEvents(event, eventElement); + // BUG: if event was really short, need to put title back in span + } + } + }); + } + + +} + + +function countForwardSegs(levels) { + var i, j, k, level, segForward, segBack; + for (i=levels.length-1; i>0; i--) { + level = levels[i]; + for (j=0; j"); + var elements; + var segmentContainer = getDaySegmentContainer(); + var i; + var segCnt = segs.length; + var element; + tempContainer[0].innerHTML = daySegHTML(segs); // faster than .html() + elements = tempContainer.children(); + segmentContainer.append(elements); + daySegElementResolve(segs, elements); + daySegCalcHSides(segs); + daySegSetWidths(segs); + daySegCalcHeights(segs); + daySegSetTops(segs, getRowTops(getRowDivs())); + elements = []; + for (i=0; i" + + "
"; + if (!event.allDay && seg.isStart) { + html += + "" + + htmlEscape(formatDates(event.start, event.end, opt('timeFormat'))) + + ""; + } + html += + "" + htmlEscape(event.title) + "" + + "
"; + if (seg.isEnd && isEventResizable(event)) { + html += + "
" + + "   " + // makes hit area a lot better for IE6/7 + "
"; + } + html += + ""; + seg.left = left; + seg.outerWidth = right - left; + seg.startCol = leftCol; + seg.endCol = rightCol + 1; // needs to be exclusive + } + return html; + } + + + function daySegElementResolve(segs, elements) { // sets seg.element + var i; + var segCnt = segs.length; + var seg; + var event; + var element; + var triggerRes; + for (i=0; i div'); // optimal selector? + } + return rowDivs; + } + + + function getRowTops(rowDivs) { + var i; + var rowCnt = rowDivs.length; + var tops = []; + for (i=0; i selection for IE + element + .mousedown(function(ev) { // prevent native selection for others + ev.preventDefault(); + }) + .click(function(ev) { + if (isResizing) { + ev.preventDefault(); // prevent link from being visited (only method that worked in IE6) + ev.stopImmediatePropagation(); // prevent fullcalendar eventClick handler from being called + // (eventElementHandlers needs to be bound after resizableDayEvent) + } + }); + + handle.mousedown(function(ev) { + if (ev.which != 1) { + return; // needs to be left mouse button + } + isResizing = true; + var hoverListener = t.getHoverListener(); + var rowCnt = getRowCnt(); + var colCnt = getColCnt(); + var dis = rtl ? -1 : 1; + var dit = rtl ? colCnt-1 : 0; + var elementTop = element.css('top'); + var dayDelta; + var helpers; + var eventCopy = $.extend({}, event); + var minCell = dateCell(event.start); + clearSelection(); + $('body') + .css('cursor', direction + '-resize') + .one('mouseup', mouseup); + trigger('eventResizeStart', this, event, ev); + hoverListener.start(function(cell, origCell) { + if (cell) { + var r = Math.max(minCell.row, cell.row); + var c = cell.col; + if (rowCnt == 1) { + r = 0; // hack for all-day area in agenda views + } + if (r == minCell.row) { + if (rtl) { + c = Math.min(minCell.col, c); + }else{ + c = Math.max(minCell.col, c); + } + } + dayDelta = (r*7 + c*dis+dit) - (origCell.row*7 + origCell.col*dis+dit); + var newEnd = addDays(eventEnd(event), dayDelta, true); + if (dayDelta) { + eventCopy.end = newEnd; + var oldHelpers = helpers; + helpers = renderTempDaySegs(compileDaySegs([eventCopy]), seg.row, elementTop); + helpers.find('*').css('cursor', direction + '-resize'); + if (oldHelpers) { + oldHelpers.remove(); + } + hideEvents(event); + }else{ + if (helpers) { + showEvents(event); + helpers.remove(); + helpers = null; + } + } + clearOverlays(); + renderDayOverlay(event.start, addDays(cloneDate(newEnd), 1)); // coordinate grid already rebuild at hoverListener.start + } + }, ev); + + function mouseup(ev) { + trigger('eventResizeStop', this, event, ev); + $('body').css('cursor', ''); + hoverListener.stop(); + clearOverlays(); + if (dayDelta) { + eventResize(this, event, dayDelta, 0, ev); + // event redraw will clear helpers + } + // otherwise, the drag handler already restored the old events + + setTimeout(function() { // make this happen after the element's click event + isResizing = false; + },0); + } + + }); + } + + +} + +;; + +//BUG: unselect needs to be triggered when events are dragged+dropped + +function SelectionManager() { + var t = this; + + + // exports + t.select = select; + t.unselect = unselect; + t.reportSelection = reportSelection; + t.daySelectionMousedown = daySelectionMousedown; + + + // imports + var opt = t.opt; + var trigger = t.trigger; + var defaultSelectionEnd = t.defaultSelectionEnd; + var renderSelection = t.renderSelection; + var clearSelection = t.clearSelection; + + + // locals + var selected = false; + + + + // unselectAuto + if (opt('selectable') && opt('unselectAuto')) { + $(document).mousedown(function(ev) { + var ignore = opt('unselectCancel'); + if (ignore) { + if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match + return; + } + } + unselect(ev); + }); + } + + + function select(startDate, endDate, allDay) { + unselect(); + if (!endDate) { + endDate = defaultSelectionEnd(startDate, allDay); + } + renderSelection(startDate, endDate, allDay); + reportSelection(startDate, endDate, allDay); + } + + + function unselect(ev) { + if (selected) { + selected = false; + clearSelection(); + trigger('unselect', null, ev); + } + } + + + function reportSelection(startDate, endDate, allDay, ev) { + selected = true; + trigger('select', null, startDate, endDate, allDay, ev); + } + + + function daySelectionMousedown(ev) { // not really a generic manager method, oh well + var cellDate = t.cellDate; + var cellIsAllDay = t.cellIsAllDay; + var hoverListener = t.getHoverListener(); + var reportDayClick = t.reportDayClick; // this is hacky and sort of weird + if (ev.which == 1 && opt('selectable')) { // which==1 means left mouse button + unselect(ev); + var _mousedownElement = this; + var dates; + hoverListener.start(function(cell, origCell) { // TODO: maybe put cellDate/cellIsAllDay info in cell + clearSelection(); + if (cell && cellIsAllDay(cell)) { + dates = [ cellDate(origCell), cellDate(cell) ].sort(cmp); + renderSelection(dates[0], dates[1], true); + }else{ + dates = null; + } + }, ev); + $(document).one('mouseup', function(ev) { + hoverListener.stop(); + if (dates) { + if (+dates[0] == +dates[1]) { + reportDayClick(dates[0], true, ev); + } + reportSelection(dates[0], dates[1], true, ev); + } + }); + } + } + + +} + +;; + +function OverlayManager() { + var t = this; + + + // exports + t.renderOverlay = renderOverlay; + t.clearOverlays = clearOverlays; + + + // locals + var usedOverlays = []; + var unusedOverlays = []; + + + function renderOverlay(rect, parent) { + var e = unusedOverlays.shift(); + if (!e) { + e = $("
"); + } + if (e[0].parentNode != parent[0]) { + e.appendTo(parent); + } + usedOverlays.push(e.css(rect).show()); + return e; + } + + + function clearOverlays() { + var e; + while (e = usedOverlays.shift()) { + unusedOverlays.push(e.hide().unbind()); + } + } + + +} + +;; + +function CoordinateGrid(buildFunc) { + + var t = this; + var rows; + var cols; + + + t.build = function() { + rows = []; + cols = []; + buildFunc(rows, cols); + }; + + + t.cell = function(x, y) { + var rowCnt = rows.length; + var colCnt = cols.length; + var i, r=-1, c=-1; + for (i=0; i= rows[i][0] && y < rows[i][1]) { + r = i; + break; + } + } + for (i=0; i= cols[i][0] && x < cols[i][1]) { + c = i; + break; + } + } + return (r>=0 && c>=0) ? { row:r, col:c } : null; + }; + + + t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 is inclusive + var origin = originElement.offset(); + return { + top: rows[row0][0] - origin.top, + left: cols[col0][0] - origin.left, + width: cols[col1][1] - cols[col0][0], + height: rows[row1][1] - rows[row0][0] + }; + }; + +} + +;; + +function HoverListener(coordinateGrid) { + + + var t = this; + var bindType; + var change; + var firstCell; + var cell; + + + t.start = function(_change, ev, _bindType) { + change = _change; + firstCell = cell = null; + coordinateGrid.build(); + mouse(ev); + bindType = _bindType || 'mousemove'; + $(document).bind(bindType, mouse); + }; + + + function mouse(ev) { + _fixUIEvent(ev); // see below + var newCell = coordinateGrid.cell(ev.pageX, ev.pageY); + if (!newCell != !cell || newCell && (newCell.row != cell.row || newCell.col != cell.col)) { + if (newCell) { + if (!firstCell) { + firstCell = newCell; + } + change(newCell, firstCell, newCell.row-firstCell.row, newCell.col-firstCell.col); + }else{ + change(newCell, firstCell); + } + cell = newCell; + } + } + + + t.stop = function() { + $(document).unbind(bindType, mouse); + return cell; + }; + + +} + + + +// this fix was only necessary for jQuery UI 1.8.16 (and jQuery 1.7 or 1.7.1) +// upgrading to jQuery UI 1.8.17 (and using either jQuery 1.7 or 1.7.1) fixed the problem +// but keep this in here for 1.8.16 users +// and maybe remove it down the line + +function _fixUIEvent(event) { // for issue 1168 + if (event.pageX === undefined) { + event.pageX = event.originalEvent.pageX; + event.pageY = event.originalEvent.pageY; + } +} +;; + +function HorizontalPositionCache(getElement) { + + var t = this, + elements = {}, + lefts = {}, + rights = {}; + + function e(i) { + return elements[i] = elements[i] || getElement(i); + } + + t.left = function(i) { + return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i]; + }; + + t.right = function(i) { + return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i]; + }; + + t.clear = function() { + elements = {}; + lefts = {}; + rights = {}; + }; + +} + +;; + +})(jQuery); \ No newline at end of file diff --git a/app/assets/javascripts/fullcalendar.min.js b/app/assets/javascripts/fullcalendar.min.js new file mode 100644 index 0000000..c3e0834 --- /dev/null +++ b/app/assets/javascripts/fullcalendar.min.js @@ -0,0 +1,7 @@ +/*! + * FullCalendar v1.6.1 + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ +(function(t,e){function n(e){t.extend(!0,ye,e)}function r(n,r,l){function u(t){G?(S(),C(),R(),b(t)):f()}function f(){K=r.theme?"ui":"fc",n.addClass("fc"),r.isRTL?n.addClass("fc-rtl"):n.addClass("fc-ltr"),r.theme&&n.addClass("ui-widget"),G=t("
").prependTo(n),$=new a(Z,r),Q=$.render(),Q&&n.prepend(Q),y(r.defaultView),t(window).resize(x),m()||v()}function v(){setTimeout(function(){!te.start&&m()&&b()},0)}function h(){t(window).unbind("resize",x),$.destroy(),G.remove(),n.removeClass("fc fc-rtl ui-widget")}function g(){return 0!==se.offsetWidth}function m(){return 0!==t("body")[0].offsetWidth}function y(e){if(!te||e!=te.name){ue++,W();var n,r=te;r?((r.beforeHide||I)(),q(G,G.height()),r.element.hide()):q(G,1),G.css("overflow","hidden"),te=ce[e],te?te.element.show():te=ce[e]=new De[e](n=re=t("
").appendTo(G),Z),r&&$.deactivateButton(r.name),$.activateButton(e),b(),G.css("overflow",""),r&&q(G,1),n||(te.afterShow||I)(),ue--}}function b(t){if(g()){ue++,W(),ne===e&&S();var r=!1;!te.start||t||te.start>fe||fe>=te.end?(te.render(fe,t||0),E(!0),r=!0):te.sizeDirty?(te.clearEvents(),E(),r=!0):te.eventsDirty&&(te.clearEvents(),r=!0),te.sizeDirty=!1,te.eventsDirty=!1,T(r),ee=n.outerWidth(),$.updateTitle(te.title);var a=new Date;a>=te.start&&te.end>a?$.disableButton("today"):$.enableButton("today"),ue--,te.trigger("viewDisplay",se)}}function M(){C(),g()&&(S(),E(),W(),te.clearEvents(),te.renderEvents(de),te.sizeDirty=!1)}function C(){t.each(ce,function(t,e){e.sizeDirty=!0})}function S(){ne=r.contentHeight?r.contentHeight:r.height?r.height-(Q?Q.height():0)-L(G):Math.round(G.width()/Math.max(r.aspectRatio,.5))}function E(t){ue++,te.setHeight(ne,t),re&&(re.css("position","relative"),re=null),te.setWidth(G.width(),t),ue--}function x(){if(!ue)if(te.start){var t=++le;setTimeout(function(){t==le&&!ue&&g()&&ee!=(ee=n.outerWidth())&&(ue++,M(),te.trigger("windowResize",se),ue--)},200)}else v()}function T(t){!r.lazyFetching||oe(te.visStart,te.visEnd)?k():t&&F()}function k(){ie(te.visStart,te.visEnd)}function H(t){de=t,F()}function z(t){F(t)}function F(t){R(),g()&&(te.clearEvents(),te.renderEvents(de,t),te.eventsDirty=!1)}function R(){t.each(ce,function(t,e){e.eventsDirty=!0})}function N(t,n,r){te.select(t,n,r===e?!0:r)}function W(){te&&te.unselect()}function A(){b(-1)}function _(){b(1)}function O(){i(fe,-1),b()}function B(){i(fe,1),b()}function Y(){fe=new Date,b()}function j(t,e,n){t instanceof Date?fe=d(t):p(fe,t,e,n),b()}function P(t,n,r){t!==e&&i(fe,t),n!==e&&s(fe,n),r!==e&&c(fe,r),b()}function J(){return d(fe)}function V(){return te}function X(t,n){return n===e?r[t]:(("height"==t||"contentHeight"==t||"aspectRatio"==t)&&(r[t]=n,M()),e)}function U(t,n){return r[t]?r[t].apply(n||se,Array.prototype.slice.call(arguments,2)):e}var Z=this;Z.options=r,Z.render=u,Z.destroy=h,Z.refetchEvents=k,Z.reportEvents=H,Z.reportEventChange=z,Z.rerenderEvents=F,Z.changeView=y,Z.select=N,Z.unselect=W,Z.prev=A,Z.next=_,Z.prevYear=O,Z.nextYear=B,Z.today=Y,Z.gotoDate=j,Z.incrementDate=P,Z.formatDate=function(t,e){return w(t,e,r)},Z.formatDates=function(t,e,n){return D(t,e,n,r)},Z.getDate=J,Z.getView=V,Z.option=X,Z.trigger=U,o.call(Z,r,l);var $,Q,G,K,te,ee,ne,re,ae,oe=Z.isFetchNeeded,ie=Z.fetchEvents,se=n[0],ce={},le=0,ue=0,fe=new Date,de=[];p(fe,r.year,r.month,r.date),r.droppable&&t(document).bind("dragstart",function(e,n){var a=e.target,o=t(a);if(!o.parents(".fc").length){var i=r.dropAccept;(t.isFunction(i)?i.call(a,o):o.is(i))&&(ae=a,te.dragStart(ae,e,n))}}).bind("dragstop",function(t,e){ae&&(te.dragStop(ae,t,e),ae=null)})}function a(n,r){function a(){v=r.theme?"ui":"fc";var n=r.header;return n?h=t("").append(t("").append(i("left")).append(i("center")).append(i("right"))):e}function o(){h.remove()}function i(e){var a=t(" + + + \ No newline at end of file diff --git a/app/views/admin/calendar_types/_form.html.erb b/app/views/admin/calendar_types/_form.html.erb new file mode 100644 index 0000000..df75bce --- /dev/null +++ b/app/views/admin/calendar_types/_form.html.erb @@ -0,0 +1,10 @@ +<%= label_tag("color", t("calendar.color")) %> +
+ <%= f.text_field :color, id: "color", :class => "color-picker miniColors input-small", :size => "7", :maxlength => "7", :autocomplete=>"off",:value=>"9100FF" %> +
+<%= f.fields_for :title_translations do |f| %> + <% @site_valid_locales.each do |locale| %> + <%= label_tag "name-#{locale}", "#{t(:name)} (#{t(locale)})" %> + <%= f.text_field locale, :class => 'input-large', :value => (@category.title_translations[locale] rescue ''), placeholder: t(:name), id: locale %> + <% end %> +<% end %> diff --git a/app/views/admin/calendar_types/create.js.erb b/app/views/admin/calendar_types/create.js.erb new file mode 100644 index 0000000..bac3cb1 --- /dev/null +++ b/app/views/admin/calendar_types/create.js.erb @@ -0,0 +1 @@ +$.pageslide.close(); \ No newline at end of file diff --git a/app/views/admin/calendar_types/edit.html.erb b/app/views/admin/calendar_types/edit.html.erb new file mode 100644 index 0000000..e69de29 diff --git a/app/views/admin/calendar_types/index.html.erb b/app/views/admin/calendar_types/index.html.erb new file mode 100644 index 0000000..5d20261 --- /dev/null +++ b/app/views/admin/calendar_types/index.html.erb @@ -0,0 +1,57 @@ +<%= stylesheet_link_tag "jquery.miniColors" %> + +<%= javascript_include_tag "jquery.miniColors.min" %> + +
+
"),o=r.header[e];return o&&t.each(o.split(" "),function(e){e>0&&a.append("");var o;t.each(this.split(","),function(e,i){if("title"==i)a.append("

 

"),o&&o.addClass(v+"-corner-right"),o=null;else{var s;if(n[i]?s=n[i]:De[i]&&(s=function(){u.removeClass(v+"-state-hover"),n.changeView(i)}),s){var c=r.theme?J(r.buttonIcons,i):null,l=J(r.buttonText,i),u=t(""+(c?""+"":l)+"").click(function(){u.hasClass(v+"-state-disabled")||s()}).mousedown(function(){u.not("."+v+"-state-active").not("."+v+"-state-disabled").addClass(v+"-state-down")}).mouseup(function(){u.removeClass(v+"-state-down")}).hover(function(){u.not("."+v+"-state-active").not("."+v+"-state-disabled").addClass(v+"-state-hover")},function(){u.removeClass(v+"-state-hover").removeClass(v+"-state-down")}).appendTo(a);U(u),o||u.addClass(v+"-corner-left"),o=u}}}),o&&o.addClass(v+"-corner-right")}),a}function s(t){h.find("h2").html(t)}function c(t){h.find("span.fc-button-"+t).addClass(v+"-state-active")}function l(t){h.find("span.fc-button-"+t).removeClass(v+"-state-active")}function u(t){h.find("span.fc-button-"+t).addClass(v+"-state-disabled")}function f(t){h.find("span.fc-button-"+t).removeClass(v+"-state-disabled")}var d=this;d.render=a,d.destroy=o,d.updateTitle=s,d.activateButton=c,d.deactivateButton=l,d.disableButton=u,d.enableButton=f;var v,h=t([])}function o(n,r){function a(t,e){return!S||S>t||e>E}function o(t,e){S=t,E=e,W=[];var n=++F,r=z.length;R=r;for(var a=0;r>a;a++)i(z[a],n)}function i(e,r){s(e,function(a){if(r==F){if(a){n.eventDataTransform&&(a=t.map(a,n.eventDataTransform)),e.eventDataTransform&&(a=t.map(a,e.eventDataTransform));for(var o=0;a.length>o;o++)a[o].source=e,b(a[o]);W=W.concat(a)}R--,R||k(W)}})}function s(r,a){var o,i,c=we.sourceFetchers;for(o=0;c.length>o;o++){if(i=c[o](r,S,E,a),i===!0)return;if("object"==typeof i)return s(i,a),e}var l=r.events;if(l)t.isFunction(l)?(p(),l(d(S),d(E),function(t){a(t),y()})):t.isArray(l)?a(l):a();else{var u=r.url;if(u){var f=r.success,v=r.error,h=r.complete,g=t.extend({},r.data||{}),m=K(r.startParam,n.startParam),b=K(r.endParam,n.endParam);m&&(g[m]=Math.round(+S/1e3)),b&&(g[b]=Math.round(+E/1e3)),p(),t.ajax(t.extend({},Me,r,{data:g,success:function(e){e=e||[];var n=G(f,this,arguments);t.isArray(n)&&(e=n),a(e)},error:function(){G(v,this,arguments),a()},complete:function(){G(h,this,arguments),y()}}))}else a()}}function c(t){t=l(t),t&&(R++,i(t,F))}function l(n){return t.isFunction(n)||t.isArray(n)?n={events:n}:"string"==typeof n&&(n={url:n}),"object"==typeof n?(w(n),z.push(n),n):e}function u(e){z=t.grep(z,function(t){return!D(t,e)}),W=t.grep(W,function(t){return!D(t.source,e)}),k(W)}function f(t){var e,n,r=W.length,a=T().defaultEventEnd,o=t.start-t._start,i=t.end?t.end-(t._end||a(t)):0;for(e=0;r>e;e++)n=W[e],n._id==t._id&&n!=t&&(n.start=new Date(+n.start+o),n.end=t.end?n.end?new Date(+n.end+i):new Date(+a(n)+i):null,n.title=t.title,n.url=t.url,n.allDay=t.allDay,n.className=t.className,n.editable=t.editable,n.color=t.color,n.backgroudColor=t.backgroudColor,n.borderColor=t.borderColor,n.textColor=t.textColor,b(n));b(t),k(W)}function v(t,e){b(t),t.source||(e&&(H.events.push(t),t.source=H),W.push(t)),k(W)}function h(e){if(e){if(!t.isFunction(e)){var n=e+"";e=function(t){return t._id==n}}W=t.grep(W,e,!0);for(var r=0;z.length>r;r++)t.isArray(z[r].events)&&(z[r].events=t.grep(z[r].events,e,!0))}else{W=[];for(var r=0;z.length>r;r++)t.isArray(z[r].events)&&(z[r].events=[])}k(W)}function g(e){return t.isFunction(e)?t.grep(W,e):e?(e+="",t.grep(W,function(t){return t._id==e})):W}function p(){N++||x("loading",null,!0)}function y(){--N||x("loading",null,!1)}function b(t){var r=t.source||{},a=K(r.ignoreTimezone,n.ignoreTimezone);t._id=t._id||(t.id===e?"_fc"+Ce++:t.id+""),t.date&&(t.start||(t.start=t.date),delete t.date),t._start=d(t.start=m(t.start,a)),t.end=m(t.end,a),t.end&&t.end<=t.start&&(t.end=null),t._end=t.end?d(t.end):null,t.allDay===e&&(t.allDay=K(r.allDayDefault,n.allDayDefault)),t.className?"string"==typeof t.className&&(t.className=t.className.split(/\s+/)):t.className=[]}function w(t){t.className?"string"==typeof t.className&&(t.className=t.className.split(/\s+/)):t.className=[];for(var e=we.sourceNormalizers,n=0;e.length>n;n++)e[n](t)}function D(t,e){return t&&e&&M(t)==M(e)}function M(t){return("object"==typeof t?t.events||t.url:"")||t}var C=this;C.isFetchNeeded=a,C.fetchEvents=o,C.addEventSource=c,C.removeEventSource=u,C.updateEvent=f,C.renderEvent=v,C.removeEvents=h,C.clientEvents=g,C.normalizeEvent=b;for(var S,E,x=C.trigger,T=C.getView,k=C.reportEvents,H={events:[]},z=[H],F=0,R=0,N=0,W=[],A=0;r.length>A;A++)l(r[A])}function i(t,e,n){return t.setFullYear(t.getFullYear()+e),n||f(t),t}function s(t,e,n){if(+t){var r=t.getMonth()+e,a=d(t);for(a.setDate(1),a.setMonth(r),t.setMonth(r),n||f(t);t.getMonth()!=a.getMonth();)t.setDate(t.getDate()+(a>t?1:-1))}return t}function c(t,e,n){if(+t){var r=t.getDate()+e,a=d(t);a.setHours(9),a.setDate(r),t.setDate(r),n||f(t),l(t,a)}return t}function l(t,e){if(+t)for(;t.getDate()!=e.getDate();)t.setTime(+t+(e>t?1:-1)*xe)}function u(t,e){return t.setMinutes(t.getMinutes()+e),t}function f(t){return t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0),t}function d(t,e){return e?f(new Date(+t)):new Date(+t)}function v(){var t,e=0;do t=new Date(1970,e++,1);while(t.getHours());return t}function h(t,e,n){for(e=e||1;!t.getDay()||n&&1==t.getDay()||!n&&6==t.getDay();)c(t,e);return t}function g(t,e){return Math.round((d(t,!0)-d(e,!0))/Ee)}function p(t,n,r,a){n!==e&&n!=t.getFullYear()&&(t.setDate(1),t.setMonth(0),t.setFullYear(n)),r!==e&&r!=t.getMonth()&&(t.setDate(1),t.setMonth(r)),a!==e&&t.setDate(a)}function m(t,n){return"object"==typeof t?t:"number"==typeof t?new Date(1e3*t):"string"==typeof t?t.match(/^\d+(\.\d+)?$/)?new Date(1e3*parseFloat(t)):(n===e&&(n=!0),y(t,n)||(t?new Date(t):null)):null}function y(t,e){var n=t.match(/^([0-9]{4})(-([0-9]{2})(-([0-9]{2})([T ]([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2})(:?([0-9]{2}))?))?)?)?)?$/);if(!n)return null;var r=new Date(n[1],0,1);if(e||!n[13]){var a=new Date(n[1],0,1,9,0);n[3]&&(r.setMonth(n[3]-1),a.setMonth(n[3]-1)),n[5]&&(r.setDate(n[5]),a.setDate(n[5])),l(r,a),n[7]&&r.setHours(n[7]),n[8]&&r.setMinutes(n[8]),n[10]&&r.setSeconds(n[10]),n[12]&&r.setMilliseconds(1e3*Number("0."+n[12])),l(r,a)}else if(r.setUTCFullYear(n[1],n[3]?n[3]-1:0,n[5]||1),r.setUTCHours(n[7]||0,n[8]||0,n[10]||0,n[12]?1e3*Number("0."+n[12]):0),n[14]){var o=60*Number(n[16])+(n[18]?Number(n[18]):0);o*="-"==n[15]?1:-1,r=new Date(+r+1e3*60*o)}return r}function b(t){if("number"==typeof t)return 60*t;if("object"==typeof t)return 60*t.getHours()+t.getMinutes();var e=t.match(/(\d+)(?::(\d+))?\s*(\w+)?/);if(e){var n=parseInt(e[1],10);return e[3]&&(n%=12,"p"==e[3].toLowerCase().charAt(0)&&(n+=12)),60*n+(e[2]?parseInt(e[2],10):0)}}function w(t,e,n){return D(t,null,e,n)}function D(t,e,n,r){r=r||ye;var a,o,i,s,c=t,l=e,u=n.length,f="";for(a=0;u>a;a++)if(o=n.charAt(a),"'"==o){for(i=a+1;u>i;i++)if("'"==n.charAt(i)){c&&(f+=i==a+1?"'":n.substring(a+1,i),a=i);break}}else if("("==o){for(i=a+1;u>i;i++)if(")"==n.charAt(i)){var d=w(c,n.substring(a+1,i),r);parseInt(d.replace(/\D/,""),10)&&(f+=d),a=i;break}}else if("["==o){for(i=a+1;u>i;i++)if("]"==n.charAt(i)){var v=n.substring(a+1,i),d=w(c,v,r);d!=w(l,v,r)&&(f+=d),a=i;break}}else if("{"==o)c=e,l=t;else if("}"==o)c=t,l=e;else{for(i=u;i>a;i--)if(s=ke[n.substring(a,i)]){c&&(f+=s(c,r)),a=i-1;break}i==a&&c&&(f+=o)}return f}function M(t){var e,n=new Date(t.getTime());return n.setDate(n.getDate()+4-(n.getDay()||7)),e=n.getTime(),n.setMonth(0),n.setDate(1),Math.floor(Math.round((e-n)/864e5)/7)+1}function C(t){return t.end?S(t.end,t.allDay):c(d(t.start),1)}function S(t,e){return t=d(t),e||t.getHours()||t.getMinutes()?c(t,1):f(t)}function E(t,e){return 100*(e.msLength-t.msLength)+(t.event.start-e.event.start)}function x(t,e){return t.end>e.start&&t.starta;a++)o=t[a],i=o.start,s=e[a],s>n&&r>i&&(n>i?(c=d(n),u=!1):(c=i,u=!0),s>r?(l=d(r),f=!1):(l=s,f=!0),v.push({event:o,start:c,end:l,isStart:u,isEnd:f,msLength:l-c}));return v.sort(E)}function k(t){var e,n,r,a,o,i=[],s=t.length;for(e=0;s>e;e++){for(n=t[e],r=0;;){if(a=!1,i[r])for(o=0;i[r].length>o;o++)if(x(i[r][o],n)){a=!0;break}if(!a)break;r++}i[r]?i[r].push(n):i[r]=[n]}return i}function H(n,r,a){n.unbind("mouseover").mouseover(function(n){for(var o,i,s,c=n.target;c!=this;)o=c,c=c.parentNode;(i=o._fci)!==e&&(o._fci=e,s=r[i],a(s.event,s.element,s),t(n.target).trigger(n)),n.stopPropagation()})}function z(e,n,r){for(var a,o=0;e.length>o;o++)a=t(e[o]),a.width(Math.max(0,n-R(a,r)))}function F(e,n,r){for(var a,o=0;e.length>o;o++)a=t(e[o]),a.height(Math.max(0,n-L(a,r)))}function R(t,e){return N(t)+A(t)+(e?W(t):0)}function N(e){return(parseFloat(t.css(e[0],"paddingLeft",!0))||0)+(parseFloat(t.css(e[0],"paddingRight",!0))||0)}function W(e){return(parseFloat(t.css(e[0],"marginLeft",!0))||0)+(parseFloat(t.css(e[0],"marginRight",!0))||0)}function A(e){return(parseFloat(t.css(e[0],"borderLeftWidth",!0))||0)+(parseFloat(t.css(e[0],"borderRightWidth",!0))||0)}function L(t,e){return _(t)+B(t)+(e?O(t):0)}function _(e){return(parseFloat(t.css(e[0],"paddingTop",!0))||0)+(parseFloat(t.css(e[0],"paddingBottom",!0))||0)}function O(e){return(parseFloat(t.css(e[0],"marginTop",!0))||0)+(parseFloat(t.css(e[0],"marginBottom",!0))||0)}function B(e){return(parseFloat(t.css(e[0],"borderTopWidth",!0))||0)+(parseFloat(t.css(e[0],"borderBottomWidth",!0))||0)}function q(t,e){e="number"==typeof e?e+"px":e,t.each(function(t,n){n.style.cssText+=";min-height:"+e+";_height:"+e})}function I(){}function Y(t,e){return t-e}function j(t){return Math.max.apply(Math,t)}function P(t){return(10>t?"0":"")+t}function J(t,n){if(t[n]!==e)return t[n];for(var r,a=n.split(/(?=[A-Z])/),o=a.length-1;o>=0;o--)if(r=t[a[o].toLowerCase()],r!==e)return r;return t[""]}function V(t){return t.replace(/&/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""").replace(/\n/g,"
")}function X(t){return t.id+"/"+t.className+"/"+t.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/gi,"")}function U(t){t.attr("unselectable","on").css("MozUserSelect","none").bind("selectstart.ui",function(){return!1})}function Z(t){t.children().removeClass("fc-first fc-last").filter(":first-child").addClass("fc-first").end().filter(":last-child").addClass("fc-last")}function $(t,e){t.each(function(t,n){n.className=n.className.replace(/^fc-\w*/,"fc-"+Se[e.getDay()])})}function Q(t,e){var n=t.source||{},r=t.color,a=n.color,o=e("eventColor"),i=t.backgroundColor||r||n.backgroundColor||a||e("eventBackgroundColor")||o,s=t.borderColor||r||n.borderColor||a||e("eventBorderColor")||o,c=t.textColor||n.textColor||e("eventTextColor"),l=[];return i&&l.push("background-color:"+i),s&&l.push("border-color:"+s),c&&l.push("color:"+c),l.join(";")}function G(e,n,r){if(t.isFunction(e)&&(e=[e]),e){var a,o;for(a=0;e.length>a;a++)o=e[a].apply(n,r)||o;return o}}function K(){for(var t=0;arguments.length>t;t++)if(arguments[t]!==e)return arguments[t]}function te(t,e){function n(t,e){e&&(s(t,e),t.setDate(1));var n=d(t,!0);n.setDate(1);var l=s(d(n),1),u=d(n),f=d(l),v=a("firstDay"),g=a("weekends")?0:1;g&&(h(u),h(f,-1,!0)),c(u,-((u.getDay()-Math.max(v,g)+7)%7)),c(f,(7-f.getDay()+Math.max(v,g))%7);var p=Math.round((f-u)/(7*Ee));"fixed"==a("weekMode")&&(c(f,7*(6-p)),p=6),r.title=i(n,a("titleFormat")),r.start=n,r.end=l,r.visStart=u,r.visEnd=f,o(p,g?5:7,!0)}var r=this;r.render=n,re.call(r,t,e,"month");var a=r.opt,o=r.renderBasic,i=e.formatDate}function ee(t,e){function n(t,e){e&&c(t,7*e);var n=c(d(t),-((t.getDay()-a("firstDay")+7)%7)),s=c(d(n),7),l=d(n),u=d(s),f=a("weekends");f||(h(l),h(u,-1,!0)),r.title=i(l,c(d(u),-1),a("titleFormat")),r.start=n,r.end=s,r.visStart=l,r.visEnd=u,o(1,f?7:5,!1)}var r=this;r.render=n,re.call(r,t,e,"basicWeek");var a=r.opt,o=r.renderBasic,i=e.formatDates}function ne(t,e){function n(t,e){e&&(c(t,e),a("weekends")||h(t,0>e?-1:1)),r.title=i(t,a("titleFormat")),r.start=r.visStart=d(t,!0),r.end=r.visEnd=c(d(r.start),1),o(1,1,!1)}var r=this;r.render=n,re.call(r,t,e,"basicDay");var a=r.opt,o=r.renderBasic,i=e.formatDate}function re(e,n,r){function a(t,e,n){ne=t,re=e,o();var r=!P;r?i():Te(),s(n)}function o(){ce=Ee("isRTL"),ce?(le=-1,fe=re-1):(le=1,fe=0),pe=Ee("firstDay"),ye=Ee("weekends")?0:1,be=Ee("theme")?"ui":"fc",we=Ee("columnFormat"),De=Ee("weekNumbers"),Me=Ee("weekNumberTitle"),Ce="iso"!=Ee("weekNumberCalculation")?"w":"W"}function i(){Q=t("
").appendTo(e)}function s(n){var r,a,o,i,s="",c=be+"-widget-header",l=be+"-widget-content",u=B.start.getMonth(),d=f(new Date);for(s+="",De&&(s+="",r=0;ne>r;r++){for(s+="",De&&(s+=""),a=0;re>a;a++)o=F(r,a),i=["fc-day","fc-"+Se[o.getDay()],l],o.getMonth()!=u&&i.push("fc-other-month"),+o==+d&&(i.push("fc-today"),i.push(be+"-state-highlight")),s+="";s+=""}s+="
"),r=0;re>r;r++)o=F(0,r),s+="";for(s+="
"+"
"+"
"+"
",n&&(s+="
"+o.getDate()+"
"),s+="
 
",_(),I&&I.remove(),I=t(s).appendTo(e),Y=I.find("thead"),j=Y.find(".fc-day-header"),P=I.find("tbody"),J=P.find("tr"),V=P.find(".fc-day"),X=J.find("td:first-child"),$=J.eq(0).find(".fc-day-content > div"),Z(Y.add(Y.find("tr"))),Z(J),J.eq(0).addClass("fc-first"),J.filter(":last").addClass("fc-last"),De&&Y.find(".fc-week-number").text(Me),j.each(function(e,n){var r=R(e);t(n).text(Fe(r,we))}),De&&P.find(".fc-week-number > div").each(function(e,n){var r=F(e,0);t(n).text(Fe(r,Ce))}),V.each(function(e,n){var r=R(e);xe("dayRender",B,r,t(n))}),v(V)}function l(e){K=e;var n,r,a,o=K-Y.height();"variable"==Ee("weekMode")?n=r=Math.floor(o/(1==ne?2:6)):(n=Math.floor(o/ne),r=o-n*(ne-1)),X.each(function(e,o){ne>e&&(a=t(o),q(a.find("> div"),(e==ne-1?r:n)-L(a)))}),O()}function u(t){G=t,se.clear(),ee=0,De&&(ee=Y.find("th.fc-week-number").outerWidth()),te=Math.floor((G-ee)/re),z(j.slice(0,-1),te)}function v(t){t.click(h).mousedown(ze)}function h(e){if(!Ee("selectable")){var n=y(t(this).data("date"));xe("dayClick",this,n,!0,e)}}function p(t,e,n){n&&oe.build();for(var r=d(B.visStart),a=c(d(r),re),o=0;ne>o;o++){var i=new Date(Math.max(r,t)),s=new Date(Math.min(a,e));if(s>i){var l,u;ce?(l=g(s,r)*le+fe+1,u=g(i,r)*le+fe+1):(l=g(i,r),u=g(s,r)),v(m(o,l,o,u-1))}c(r,7),c(a,7)}}function m(t,n,r,a){var o=oe.rect(t,n,r,a,e);return ke(o,e)}function b(t){return d(t)}function w(t,e){p(t,c(d(e),1),!0)}function D(){He()}function M(t,e,n){var r=k(t),a=V[r.row*re+r.col];xe("dayClick",a,t,e,n)}function C(t,e){ie.start(function(t){He(),t&&m(t.row,t.col,t.row,t.col)},e)}function S(t,e,n){var r=ie.stop();if(He(),r){var a=H(r);xe("drop",t,a,!0,e,n)}}function E(t){return d(t.start)}function x(t){return se.left(t)}function T(t){return se.right(t)}function k(t){return{row:Math.floor(g(t,B.visStart)/7),col:N(t.getDay())}}function H(t){return F(t.row,t.col)}function F(t,e){return c(d(B.visStart),7*t+e*le+fe)}function R(t){return F(Math.floor(t/re),t%re)}function N(t){return(t-Math.max(pe,ye)+re)%re*le+fe}function W(t){return J.eq(t)}function A(){var t=0;return De&&(t+=ee),{left:t,right:G}}function _(){q(e,e.height())}function O(){q(e,1)}var B=this;B.renderBasic=a,B.setHeight=l,B.setWidth=u,B.renderDayOverlay=p,B.defaultSelectionEnd=b,B.renderSelection=w,B.clearSelection=D,B.reportDayClick=M,B.dragStart=C,B.dragStop=S,B.defaultEventEnd=E,B.getHoverListener=function(){return ie},B.colContentLeft=x,B.colContentRight=T,B.dayOfWeekCol=N,B.dateCell=k,B.cellDate=H,B.cellIsAllDay=function(){return!0},B.allDayRow=W,B.allDayBounds=A,B.getRowCnt=function(){return ne},B.getColCnt=function(){return re},B.getColWidth=function(){return te},B.getDaySegmentContainer=function(){return Q},ue.call(B,e,n,r),ve.call(B),de.call(B),ae.call(B);var I,Y,j,P,J,V,X,$,Q,G,K,te,ee,ne,re,oe,ie,se,ce,le,fe,pe,ye,be,we,De,Me,Ce,Ee=B.opt,xe=B.trigger,Te=B.clearEvents,ke=B.renderOverlay,He=B.clearOverlays,ze=B.daySelectionMousedown,Fe=n.formatDate;U(e.addClass("fc-grid")),oe=new he(function(e,n){var r,a,o;j.each(function(e,i){r=t(i),a=r.offset().left,e&&(o[1]=a),o=[a],n[e]=o}),o[1]=a+r.outerWidth(),J.each(function(n,i){ne>n&&(r=t(i),a=r.offset().top,n&&(o[1]=a),o=[a],e[n]=o)}),o[1]=a+r.outerHeight()}),ie=new ge(oe),se=new me(function(t){return $.eq(t)})}function ae(){function e(t,e){v(t),x(r(t),e),l("eventAfterAllRender")}function n(){h(),b().empty()}function r(e){var n,r,a,o,s,l,u=S(),f=E(),v=d(i.visStart),h=c(d(v),f),g=t.map(e,C),p=[];for(n=0;u>n;n++){for(r=k(T(e,g,v,h)),a=0;r.length>a;a++)for(o=r[a],s=0;o.length>s;s++)l=o[s],l.row=n,l.level=a,p.push(l);c(v,7),c(h,7)}return p}function a(t,e,n){u(t)&&o(t,e),n.isEnd&&f(t)&&H(t,e,n),g(t,e)}function o(t,e){var n,r=w();e.draggable({zIndex:9,delay:50,opacity:s("dragOpacity"),revertDuration:s("dragRevertDuration"),start:function(a,o){l("eventDragStart",e,t,a,o),m(t,e),r.start(function(r,a,o,i){e.draggable("option","revert",!r||!o&&!i),M(),r?(n=7*o+i*(s("isRTL")?-1:1),D(c(d(t.start),n),c(C(t),n))):n=0},a,"drag")},stop:function(a,o){r.stop(),M(),l("eventDragStop",e,t,a,o),n?y(this,t,n,0,t.allDay,a,o):(e.css("filter",""),p(t,e))}})}var i=this;i.renderEvents=e,i.compileDaySegs=r,i.clearEvents=n,i.bindDaySeg=a,fe.call(i);var s=i.opt,l=i.trigger,u=i.isEventDraggable,f=i.isEventResizable,v=i.reportEvents,h=i.reportEventClear,g=i.eventElementHandlers,p=i.showEvents,m=i.hideEvents,y=i.eventDrop,b=i.getDaySegmentContainer,w=i.getHoverListener,D=i.renderDayOverlay,M=i.clearOverlays,S=i.getRowCnt,E=i.getColCnt,x=i.renderDaySegs,H=i.resizableDayEvent}function oe(t,e){function n(t,e){e&&c(t,7*e);var n=c(d(t),-((t.getDay()-a("firstDay")+7)%7)),s=c(d(n),7),l=d(n),u=d(s),f=a("weekends");f||(h(l),h(u,-1,!0)),r.title=i(l,c(d(u),-1),a("titleFormat")),r.start=n,r.end=s,r.visStart=l,r.visEnd=u,o(f?7:5)}var r=this;r.render=n,se.call(r,t,e,"agendaWeek");var a=r.opt,o=r.renderAgenda,i=e.formatDates}function ie(t,e){function n(t,e){e&&(c(t,e),a("weekends")||h(t,0>e?-1:1));var n=d(t,!0),s=c(d(n),1);r.title=i(t,a("titleFormat")),r.start=r.visStart=n,r.end=r.visEnd=s,o(1)}var r=this;r.render=n,se.call(r,t,e,"agendaDay");var a=r.opt,o=r.renderAgenda,i=e.formatDate}function se(n,r,a){function o(t){Le=t,i(),te?nn():s(),l()}function i(){Ye=tn("theme")?"ui":"fc",Pe=tn("weekends")?0:1,je=tn("firstDay"),(Je=tn("isRTL"))?(Ve=-1,Xe=Le-1):(Ve=1,Xe=0),Ue=b(tn("minTime")),Ze=b(tn("maxTime")),$e=tn("columnFormat"),Qe=tn("weekNumbers"),Ge=tn("weekNumberTitle"),Ke="iso"!=tn("weekNumberCalculation")?"w":"W",Ne=tn("snapMinutes")||tn("slotMinutes")}function s(){var e,r,a,o,i,s=Ye+"-widget-header",c=Ye+"-widget-content",l=0==tn("slotMinutes")%15;for(e="",e+=Qe?"",r=0;Le>r;r++)e+=""+""+""+""+""+"",r=0;Le>r;r++)e+="";for(e+=""+""+""+"
":" ";for(e+=" 
 "+"
"+"
"+"
 
"+"
"+"
"+"
 
",te=t(e).appendTo(n),ee=te.find("thead"),ne=ee.find("th").slice(1,-1),re=te.find("tbody"),ae=re.find("td").slice(0,-1),oe=ae.find("div.fc-day-content div"),ie=ae.eq(0),se=ie.find("> div"),Z(ee.add(ee.find("tr"))),Z(re.add(re.find("tr"))),Se=ee.find("th:first"),Ee=te.find(".fc-agenda-gutter"),le=t("
").appendTo(n),tn("allDaySlot")?(fe=t("
").appendTo(le),e=""+""+""+""+"
"+tn("allDayText")+""+"
"+"
 
",pe=t(e).appendTo(le),ye=pe.find("tr"),D(ye.find("td")),Se=Se.add(pe.find("th:first")),Ee=Ee.add(pe.find("th.fc-agenda-gutter")),le.append("
"+"
"+"
")):fe=t([]),be=t("
").appendTo(le),we=t("
").appendTo(be),De=t("
").appendTo(we),e="",a=v(),o=u(d(a),Ze),u(a,Ue),_e=0,r=0;o>a;r++)i=a.getMinutes(),e+=""+""+""+"",u(a,tn("slotMinutes")),_e++;e+="
"+(l&&i?" ":un(a,tn("axisFormat")))+""+"
 
"+"
",Me=t(e).appendTo(we),Ce=Me.find("div:first"),M(Me.find("td")),Se=Se.add(Me.find("th:first"))}function l(){var t,e,n,r,a=f(new Date);if(Qe){var o=un(N(0),Ke);Je?o+=Ge:o=Ge+o,ee.find(".fc-week-number").text(o)}for(t=0;Le>t;t++)r=N(t),e=ne.eq(t),e.html(un(r,$e)),n=ae.eq(t),+r==+a?n.addClass(Ye+"-state-highlight fc-today"):n.removeClass(Ye+"-state-highlight fc-today"),$(e.add(n),r)}function h(t,n){t===e&&(t=ke),ke=t,fn={};var r=re.position().top,a=be.position().top,o=Math.min(t-r,Me.height()+a+1);se.height(o-L(ie)),le.css("top",r),be.height(o-a-1),Re=Ce.height()+1,We=tn("slotMinutes")/Ne,Ae=Re/We,n&&m()}function p(e){Te=e,qe.clear(),He=0,z(Se.width("").each(function(e,n){He=Math.max(He,t(n).outerWidth())}),He);var n=be[0].clientWidth;Fe=be.width()-n,Fe?(z(Ee,Fe),Ee.show().prev().removeClass("fc-last")):Ee.hide().prev().addClass("fc-last"),ze=Math.floor((n-He)/Le),z(ne.slice(0,-1),ze)}function m(){function t(){be.scrollTop(r)}var e=v(),n=d(e);n.setHours(tn("firstHour"));var r=_(e,n)+1;t(),setTimeout(t,0)}function y(){Ie=be.scrollTop()}function w(){be.scrollTop(Ie)}function D(t){t.click(C).mousedown(cn)}function M(t){t.click(C).mousedown(V)}function C(t){if(!tn("selectable")){var e=Math.min(Le-1,Math.floor((t.pageX-te.offset().left-He)/ze)),n=N(e),r=this.parentNode.className.match(/fc-slot(\d+)/);if(r){var a=parseInt(r[1])*tn("slotMinutes"),o=Math.floor(a/60);n.setHours(o),n.setMinutes(a%60+Ue),en("dayClick",ae[e],n,!1,t)}else en("dayClick",ae[e],n,!0,t)}}function S(t,e,n){n&&Oe.build();var r,a,o=d(K.visStart);Je?(r=g(e,o)*Ve+Xe+1,a=g(t,o)*Ve+Xe+1):(r=g(t,o),a=g(e,o)),r=Math.max(0,r),a=Math.min(Le,a),a>r&&D(E(0,r,0,a-1))}function E(t,e,n,r){var a=Oe.rect(t,e,n,r,le);return rn(a,le)}function x(t,e){for(var n=d(K.visStart),r=c(d(n),1),a=0;Le>a;a++){var o=new Date(Math.max(n,t)),i=new Date(Math.min(r,e));if(i>o){var s=a*Ve+Xe,l=Oe.rect(0,s,0,s,we),u=_(n,o),f=_(n,i);l.top=u,l.height=f-u,M(rn(l,we))}c(n,1),c(r,1)}}function T(t){return qe.left(t)}function k(t){return qe.right(t)}function H(t){return{row:Math.floor(g(t,K.visStart)/7),col:A(t.getDay())}}function R(t){var e=N(t.col),n=t.row;return tn("allDaySlot")&&n--,n>=0&&u(e,Ue+n*Ne),e}function N(t){return c(d(K.visStart),t*Ve+Xe)}function W(t){return tn("allDaySlot")&&!t.row}function A(t){return(t-Math.max(je,Pe)+Le)%Le*Ve+Xe}function _(t,n){if(t=d(t,!0),u(d(t),Ue)>n)return 0;if(n>=u(d(t),Ze))return Me.height();var r=tn("slotMinutes"),a=60*n.getHours()+n.getMinutes()-Ue,o=Math.floor(a/r),i=fn[o];return i===e&&(i=fn[o]=Me.find("tr:eq("+o+") td div")[0].offsetTop),Math.max(0,Math.round(i-1+Re*(a%r/r)))}function O(){return{left:He,right:Te-Fe}}function B(){return ye}function q(t){var e=d(t.start);return t.allDay?e:u(e,tn("defaultEventMinutes"))}function I(t,e){return e?d(t):u(d(t),tn("slotMinutes"))}function j(t,e,n){n?tn("allDaySlot")&&S(t,c(d(e),1),!0):P(t,e)}function P(e,n){var r=tn("selectHelper");if(Oe.build(),r){var a=g(e,K.visStart)*Ve+Xe;if(a>=0&&Le>a){var o=Oe.rect(0,a,0,a,we),i=_(e,e),s=_(e,n);if(s>i){if(o.top=i,o.height=s-i,o.left+=2,o.width-=5,t.isFunction(r)){var c=r(e,n);c&&(o.position="absolute",o.zIndex=8,xe=t(c).css(o).appendTo(we))}else o.isStart=!0,o.isEnd=!0,xe=t(ln({title:"",start:e,end:n,className:["fc-select-helper"],editable:!1},o)),xe.css("opacity",tn("dragOpacity"));xe&&(M(xe),we.append(xe),z(xe,o.width,!0),F(xe,o.height,!0))}}}else x(e,n)}function J(){an(),xe&&(xe.remove(),xe=null)}function V(e){if(1==e.which&&tn("selectable")){sn(e);var n;Be.start(function(t,e){if(J(),t&&t.col==e.col&&!W(t)){var r=R(e),a=R(t);n=[r,u(d(r),Ne),a,u(d(a),Ne)].sort(Y),P(n[0],n[3])}else n=null},e),t(document).one("mouseup",function(t){Be.stop(),n&&(+n[0]==+n[1]&&X(n[0],!1,t),on(n[0],n[3],!1,t))})}}function X(t,e,n){en("dayClick",ae[A(t.getDay())],t,e,n)}function Q(t,e){Be.start(function(t){if(an(),t)if(W(t))E(t.row,t.col,t.row,t.col);else{var e=R(t),n=u(d(e),tn("defaultEventMinutes"));x(e,n)}},e)}function G(t,e,n){var r=Be.stop();an(),r&&en("drop",t,R(r),W(r),e,n)}var K=this;K.renderAgenda=o,K.setWidth=p,K.setHeight=h,K.beforeHide=y,K.afterShow=w,K.defaultEventEnd=q,K.timePosition=_,K.dayOfWeekCol=A,K.dateCell=H,K.cellDate=R,K.cellIsAllDay=W,K.allDayRow=B,K.allDayBounds=O,K.getHoverListener=function(){return Be},K.colContentLeft=T,K.colContentRight=k,K.getDaySegmentContainer=function(){return fe},K.getSlotSegmentContainer=function(){return De},K.getMinMinute=function(){return Ue},K.getMaxMinute=function(){return Ze},K.getBodyContent=function(){return we},K.getRowCnt=function(){return 1},K.getColCnt=function(){return Le},K.getColWidth=function(){return ze},K.getSnapHeight=function(){return Ae},K.getSnapMinutes=function(){return Ne},K.defaultSelectionEnd=I,K.renderDayOverlay=S,K.renderSelection=j,K.clearSelection=J,K.reportDayClick=X,K.dragStart=Q,K.dragStop=G,ue.call(K,n,r,a),ve.call(K),de.call(K),ce.call(K);var te,ee,ne,re,ae,oe,ie,se,le,fe,pe,ye,be,we,De,Me,Ce,Se,Ee,xe,Te,ke,He,ze,Fe,Re,Ne,We,Ae,Le,_e,Oe,Be,qe,Ie,Ye,je,Pe,Je,Ve,Xe,Ue,Ze,$e,Qe,Ge,Ke,tn=K.opt,en=K.trigger,nn=K.clearEvents,rn=K.renderOverlay,an=K.clearOverlays,on=K.reportSelection,sn=K.unselect,cn=K.daySelectionMousedown,ln=K.slotSegHtml,un=r.formatDate,fn={};U(n.addClass("fc-agenda")),Oe=new he(function(e,n){function r(t){return Math.max(c,Math.min(l,t))}var a,o,i;ne.each(function(e,r){a=t(r),o=a.offset().left,e&&(i[1]=o),i=[o],n[e]=i}),i[1]=o+a.outerWidth(),tn("allDaySlot")&&(a=ye,o=a.offset().top,e[0]=[o,o+a.outerHeight()]);for(var s=we.offset().top,c=be.offset().top,l=c+be.outerHeight(),u=0;_e*We>u;u++)e.push([r(s+Ae*u),r(s+Ae*(u+1))])}),Be=new ge(Oe),qe=new me(function(t){return oe.eq(t)})}function ce(){function n(t,e){S(t);var n,r=t.length,i=[],c=[];for(n=0;r>n;n++)t[n].allDay?i.push(t[n]):c.push(t[n]);y("allDaySlot")&&(Y(a(i),e),z()),s(o(c),e),b("eventAfterAllRender")}function r(){E(),N().empty(),W().empty()}function a(e){var n,r,a,o,i=k(T(e,t.map(e,C),m.visStart,m.visEnd)),s=i.length,c=[];for(n=0;s>n;n++)for(r=i[n],a=0;r.length>a;a++)o=r[a],o.row=0,o.level=n,c.push(o);return c}function o(e){var n,r,a,o,s,l,f=P(),v=O(),h=_(),g=u(d(m.visStart),v),p=t.map(e,i),y=[];for(n=0;f>n;n++){for(r=k(T(e,p,g,u(d(g),h-v))),le(r),a=0;r.length>a;a++)for(o=r[a],s=0;o.length>s;s++)l=o[s],l.col=n,l.level=a,y.push(l);c(g,1,!0)}return y}function i(t){return t.end?d(t.end):u(d(t.start),y("defaultEventMinutes"))}function s(n,r){var a,o,i,s,c,u,f,d,h,g,p,m,w,D,M,C,S,E,x,T,k,z,F=n.length,N="",A={},_={},O=W(),Y=P();for((T=y("isRTL"))?(k=-1,z=Y-1):(k=1,z=0),a=0;F>a;a++)o=n[a],i=o.event,s=B(o.start,o.start),c=B(o.start,o.end),u=o.col,f=o.level,d=o.forward||0,h=q(u*k+z),g=I(u*k+z)-h,g=Math.min(g-6,.95*g),p=f?g/(f+d+1):d?2*(g/(d+1)-6):g,m=h+g/(f+d+1)*f*k+(T?g-p:0),o.top=s,o.left=m,o.outerWidth=p,o.outerHeight=c-s,N+=l(i,o); +for(O[0].innerHTML=N,w=O.children(),a=0;F>a;a++)o=n[a],i=o.event,D=t(w[a]),M=b("eventRender",i,i,D),M===!1?D.remove():(M&&M!==!0&&(D.remove(),D=t(M).css({position:"absolute",top:o.top,left:o.left}).appendTo(O)),o.element=D,i._id===r?v(i,D,o):D[0]._fci=a,G(i,D));for(H(O,n,v),a=0;F>a;a++)o=n[a],(D=o.element)&&(S=A[C=o.key=X(D[0])],o.vsides=S===e?A[C]=L(D,!0):S,S=_[C],o.hsides=S===e?_[C]=R(D,!0):S,E=D.find(".fc-event-title"),E.length&&(o.contentTop=E[0].offsetTop));for(a=0;F>a;a++)o=n[a],(D=o.element)&&(D[0].style.width=Math.max(0,o.outerWidth-o.hsides)+"px",x=Math.max(0,o.outerHeight-o.vsides),D[0].style.height=x+"px",i=o.event,o.contentTop!==e&&10>x-o.contentTop&&(D.find("div.fc-event-time").text(ie(i.start,y("timeFormat"))+" - "+i.title),D.find("div.fc-event-title").remove()),b("eventAfterRender",i,i,D))}function l(t,e){var n="<",r=t.url,a=Q(t,y),o=["fc-event","fc-event-vert"];return w(t)&&o.push("fc-event-draggable"),e.isStart&&o.push("fc-event-start"),e.isEnd&&o.push("fc-event-end"),o=o.concat(t.className),t.source&&(o=o.concat(t.source.className||[])),n+=r?"a href='"+V(t.url)+"'":"div",n+=" class='"+o.join(" ")+"'"+" style='position:absolute;z-index:8;top:"+e.top+"px;left:"+e.left+"px;"+a+"'"+">"+"
"+"
"+V(se(t.start,t.end,y("timeFormat")))+"
"+"
"+V(t.title)+"
"+"
"+"
",e.isEnd&&D(t)&&(n+="
=
"),n+=""}function f(t,e,n){w(t)&&h(t,e,n.isStart),n.isEnd&&D(t)&&j(t,e,n),x(t,e)}function v(t,e,n){var r=e.find("div.fc-event-time");w(t)&&g(t,e,r),n.isEnd&&D(t)&&p(t,e,r),x(t,e)}function h(t,e,n){function r(){s||(e.width(a).height("").draggable("option","grid",null),s=!0)}var a,o,i,s=!0,l=y("isRTL")?-1:1,u=A(),f=J(),v=U(),h=Z(),g=O();e.draggable({zIndex:9,opacity:y("dragOpacity","month"),revertDuration:y("dragRevertDuration"),start:function(g,p){b("eventDragStart",e,t,g,p),te(t,e),a=e.width(),u.start(function(a,u,g,p){ae(),a?(o=!1,i=p*l,a.row?n?s&&(e.width(f-10),F(e,v*Math.round((t.end?(t.end-t.start)/Te:y("defaultEventMinutes"))/h)),e.draggable("option","grid",[f,1]),s=!1):o=!0:(re(c(d(t.start),i),c(C(t),i)),r()),o=o||s&&!i):(r(),o=!0),e.draggable("option","revert",o)},g,"drag")},stop:function(n,a){if(u.stop(),ae(),b("eventDragStop",e,t,n,a),o)r(),e.css("filter",""),K(t,e);else{var c=0;s||(c=Math.round((e.offset().top-$().offset().top)/v)*h+g-(60*t.start.getHours()+t.start.getMinutes())),ee(this,t,i,c,s,n,a)}}})}function g(t,e,n){function r(e){var r,a=u(d(t.start),e);t.end&&(r=u(d(t.end),e)),n.text(se(a,r,y("timeFormat")))}function a(){f&&(n.css("display",""),e.draggable("option","grid",[p,m]),f=!1)}var o,i,s,l,f=!1,v=y("isRTL")?-1:1,h=A(),g=P(),p=J(),m=U(),w=Z();e.draggable({zIndex:9,scroll:!1,grid:[p,m],axis:1==g?"y":!1,opacity:y("dragOpacity"),revertDuration:y("dragRevertDuration"),start:function(r,u){b("eventDragStart",e,t,r,u),te(t,e),o=e.position(),s=l=0,h.start(function(r,o,s,l){e.draggable("option","revert",!r),ae(),r&&(i=l*v,y("allDaySlot")&&!r.row?(f||(f=!0,n.hide(),e.draggable("option","grid",null)),re(c(d(t.start),i),c(C(t),i))):a())},r,"drag")},drag:function(t,e){s=Math.round((e.position.top-o.top)/m)*w,s!=l&&(f||r(s),l=s)},stop:function(n,c){var l=h.stop();ae(),b("eventDragStop",e,t,n,c),l&&(i||s||f)?ee(this,t,i,f?0:s,f,n,c):(a(),e.css("filter",""),e.css(o),r(0),K(t,e))}})}function p(t,e,n){var r,a,o=U(),i=Z();e.resizable({handles:{s:".ui-resizable-handle"},grid:o,start:function(n,o){r=a=0,te(t,e),e.css("z-index",9),b("eventResizeStart",this,t,n,o)},resize:function(s,c){r=Math.round((Math.max(o,e.height())-c.originalSize.height)/o),r!=a&&(n.text(se(t.start,r||t.end?u(M(t),i*r):null,y("timeFormat"))),a=r)},stop:function(n,a){b("eventResizeStop",this,t,n,a),r?ne(this,t,0,i*r,n,a):(e.css("z-index",8),K(t,e))}})}var m=this;m.renderEvents=n,m.compileDaySegs=a,m.clearEvents=r,m.slotSegHtml=l,m.bindDaySeg=f,fe.call(m);var y=m.opt,b=m.trigger,w=m.isEventDraggable,D=m.isEventResizable,M=m.eventEnd,S=m.reportEvents,E=m.reportEventClear,x=m.eventElementHandlers,z=m.setHeight,N=m.getDaySegmentContainer,W=m.getSlotSegmentContainer,A=m.getHoverListener,_=m.getMaxMinute,O=m.getMinMinute,B=m.timePosition,q=m.colContentLeft,I=m.colContentRight,Y=m.renderDaySegs,j=m.resizableDayEvent,P=m.getColCnt,J=m.getColWidth,U=m.getSnapHeight,Z=m.getSnapMinutes,$=m.getBodyContent,G=m.reportEventElement,K=m.showEvents,te=m.hideEvents,ee=m.eventDrop,ne=m.eventResize,re=m.renderDayOverlay,ae=m.clearOverlays,oe=m.calendar,ie=oe.formatDate,se=oe.formatDates}function le(t){var e,n,r,a,o,i;for(e=t.length-1;e>0;e--)for(a=t[e],n=0;a.length>n;n++)for(o=a[n],r=0;t[e-1].length>r;r++)i=t[e-1][r],x(o,i)&&(i.forward=Math.max(i.forward||0,(o.forward||0)+1))}function ue(t,n,r){function a(t,e){var n=F[t];return"object"==typeof n?J(n,e||r):n}function o(t,e){return n.trigger.apply(n,[t,e||S].concat(Array.prototype.slice.call(arguments,2),[S]))}function i(t){return l(t)&&!a("disableDragging")}function s(t){return l(t)&&!a("disableResizing")}function l(t){return K(t.editable,(t.source||{}).editable,a("editable"))}function f(t){k={};var e,n,r=t.length;for(e=0;r>e;e++)n=t[e],k[n._id]?k[n._id].push(n):k[n._id]=[n]}function v(t){return t.end?d(t.end):E(t)}function h(t,e){H.push(e),z[t._id]?z[t._id].push(e):z[t._id]=[e]}function g(){H=[],z={}}function p(t,n){n.click(function(r){return n.hasClass("ui-draggable-dragging")||n.hasClass("ui-resizable-resizing")?e:o("eventClick",this,t,r)}).hover(function(e){o("eventMouseover",this,t,e)},function(e){o("eventMouseout",this,t,e)})}function m(t,e){b(t,e,"show")}function y(t,e){b(t,e,"hide")}function b(t,e,n){var r,a=z[t._id],o=a.length;for(r=0;o>r;r++)e&&a[r][0]==e[0]||a[r][n]()}function w(t,e,n,r,a,i,s){var c=e.allDay,l=e._id;M(k[l],n,r,a),o("eventDrop",t,e,n,r,a,function(){M(k[l],-n,-r,c),T(l)},i,s),T(l)}function D(t,e,n,r,a,i){var s=e._id;C(k[s],n,r),o("eventResize",t,e,n,r,function(){C(k[s],-n,-r),T(s)},a,i),T(s)}function M(t,n,r,a){r=r||0;for(var o,i=t.length,s=0;i>s;s++)o=t[s],a!==e&&(o.allDay=a),u(c(o.start,n,!0),r),o.end&&(o.end=u(c(o.end,n,!0),r)),x(o,F)}function C(t,e,n){n=n||0;for(var r,a=t.length,o=0;a>o;o++)r=t[o],r.end=u(c(v(r),e,!0),n),x(r,F)}var S=this;S.element=t,S.calendar=n,S.name=r,S.opt=a,S.trigger=o,S.isEventDraggable=i,S.isEventResizable=s,S.reportEvents=f,S.eventEnd=v,S.reportEventElement=h,S.reportEventClear=g,S.eventElementHandlers=p,S.showEvents=m,S.hideEvents=y,S.eventDrop=w,S.eventResize=D;var E=S.defaultEventEnd,x=n.normalizeEvent,T=n.reportEventChange,k={},H=[],z={},F=n.options}function fe(){function n(t,e){var n,r,c,d,p,m,y,b,w=B(),D=T(),M=k(),C=0,S=t.length;for(w[0].innerHTML=a(t),o(t,w.children()),i(t),s(t,w,e),l(t),u(t),f(t),n=v(),r=0;D>r;r++){for(c=0,d=[],p=0;M>p;p++)d[p]=0;for(;S>C&&(m=t[C]).row==r;){for(y=j(d.slice(m.startCol,m.endCol)),m.top=y,y+=m.outerHeight,b=m.startCol;m.endCol>b;b++)d[b]=y;C++}n[r].height(j(d))}g(t,h(n))}function r(e,n,r){var i,s,c,d=t("
"),p=B(),m=e.length;for(d[0].innerHTML=a(e),i=d.children(),p.append(i),o(e,i),l(e),u(e),f(e),g(e,h(v())),i=[],s=0;m>s;s++)c=e[s].element,c&&(e[s].row===n&&c.css("top",r),i.push(c[0]));return t(i)}function a(t){var e,n,r,a,o,i,s,c,l,u,f=y("isRTL"),d=t.length,v=F(),h=v.left,g=v.right,p="";for(e=0;d>e;e++)n=t[e],r=n.event,o=["fc-event","fc-event-hori"],w(r)&&o.push("fc-event-draggable"),n.isStart&&o.push("fc-event-start"),n.isEnd&&o.push("fc-event-end"),f?(i=A(n.end.getDay()-1),s=A(n.start.getDay()),c=n.isEnd?N(i):h,l=n.isStart?W(s):g):(i=A(n.start.getDay()),s=A(n.end.getDay()-1),c=n.isStart?N(i):h,l=n.isEnd?W(s):g),o=o.concat(r.className),r.source&&(o=o.concat(r.source.className||[])),a=r.url,u=Q(r,y),p+=a?""+"
",!r.allDay&&n.isStart&&(p+=""+V(I(r.start,r.end,y("timeFormat")))+""),p+=""+V(r.title)+""+"
",n.isEnd&&D(r)&&(p+="
"+"   "+"
"),p+="",n.left=c,n.outerWidth=l-c,n.startCol=i,n.endCol=s+1;return p}function o(e,n){var r,a,o,i,s,c=e.length;for(r=0;c>r;r++)a=e[r],o=a.event,i=t(n[r]),s=b("eventRender",o,o,i),s===!1?i.remove():(s&&s!==!0&&(s=t(s).css({position:"absolute",left:a.left}),i.replaceWith(s),i=s),a.element=i)}function i(t){var e,n,r,a=t.length;for(e=0;a>e;e++)n=t[e],r=n.element,r&&C(n.event,r)}function s(t,e,n){var r,a,o,i,s=t.length;for(r=0;s>r;r++)a=t[r],o=a.element,o&&(i=a.event,i._id===n?q(i,o,a):o[0]._fci=r);H(e,t,q)}function l(t){var n,r,a,o,i,s=t.length,c={};for(n=0;s>n;n++)r=t[n],a=r.element,a&&(o=r.key=X(a[0]),i=c[o],i===e&&(i=c[o]=R(a,!0)),r.hsides=i)}function u(t){var e,n,r,a=t.length;for(e=0;a>e;e++)n=t[e],r=n.element,r&&(r[0].style.width=Math.max(0,n.outerWidth-n.hsides)+"px")}function f(t){var n,r,a,o,i,s=t.length,c={};for(n=0;s>n;n++)r=t[n],a=r.element,a&&(o=r.key,i=c[o],i===e&&(i=c[o]=O(a)),r.outerHeight=a[0].offsetHeight+i)}function v(){var t,e=T(),n=[];for(t=0;e>t;t++)n[t]=z(t).find("div.fc-day-content > div");return n}function h(t){var e,n=t.length,r=[];for(e=0;n>e;e++)r[e]=t[e][0].offsetTop;return r}function g(t,e){var n,r,a,o,i=t.length;for(n=0;i>n;n++)r=t[n],a=r.element,a&&(a[0].style.top=e[r.row]+(r.top||0)+"px",o=r.event,b("eventAfterRender",o,o,a))}function p(e,n,a){var o=y("isRTL"),i=o?"w":"e",s=n.find(".ui-resizable-"+i),l=!1;U(n),n.mousedown(function(t){t.preventDefault()}).click(function(t){l&&(t.preventDefault(),t.stopImmediatePropagation())}),s.mousedown(function(s){function u(n){b("eventResizeStop",this,e,n),t("body").css("cursor",""),h.stop(),P(),f&&x(this,e,f,0,n),setTimeout(function(){l=!1},0)}if(1==s.which){l=!0;var f,v,h=m.getHoverListener(),g=T(),p=k(),y=o?-1:1,w=o?p-1:0,D=n.css("top"),C=t.extend({},e),H=L(e.start);J(),t("body").css("cursor",i+"-resize").one("mouseup",u),b("eventResizeStart",this,e,s),h.start(function(t,n){if(t){var s=Math.max(H.row,t.row),l=t.col;1==g&&(s=0),s==H.row&&(l=o?Math.min(H.col,l):Math.max(H.col,l)),f=7*s+l*y+w-(7*n.row+n.col*y+w);var u=c(M(e),f,!0);if(f){C.end=u;var h=v;v=r(_([C]),a.row,D),v.find("*").css("cursor",i+"-resize"),h&&h.remove(),E(e)}else v&&(S(e),v.remove(),v=null);P(),Y(e.start,c(d(u),1))}},s)}})}var m=this;m.renderDaySegs=n,m.resizableDayEvent=p;var y=m.opt,b=m.trigger,w=m.isEventDraggable,D=m.isEventResizable,M=m.eventEnd,C=m.reportEventElement,S=m.showEvents,E=m.hideEvents,x=m.eventResize,T=m.getRowCnt,k=m.getColCnt;m.getColWidth;var z=m.allDayRow,F=m.allDayBounds,N=m.colContentLeft,W=m.colContentRight,A=m.dayOfWeekCol,L=m.dateCell,_=m.compileDaySegs,B=m.getDaySegmentContainer,q=m.bindDaySeg,I=m.calendar.formatDates,Y=m.renderDayOverlay,P=m.clearOverlays,J=m.clearSelection}function de(){function e(t,e,a){n(),e||(e=c(t,a)),l(t,e,a),r(t,e,a)}function n(t){f&&(f=!1,u(),s("unselect",null,t))}function r(t,e,n,r){f=!0,s("select",null,t,e,n,r)}function a(e){var a=o.cellDate,s=o.cellIsAllDay,c=o.getHoverListener(),f=o.reportDayClick;if(1==e.which&&i("selectable")){n(e);var d;c.start(function(t,e){u(),t&&s(t)?(d=[a(e),a(t)].sort(Y),l(d[0],d[1],!0)):d=null},e),t(document).one("mouseup",function(t){c.stop(),d&&(+d[0]==+d[1]&&f(d[0],!0,t),r(d[0],d[1],!0,t))})}}var o=this;o.select=e,o.unselect=n,o.reportSelection=r,o.daySelectionMousedown=a;var i=o.opt,s=o.trigger,c=o.defaultSelectionEnd,l=o.renderSelection,u=o.clearSelection,f=!1;i("selectable")&&i("unselectAuto")&&t(document).mousedown(function(e){var r=i("unselectCancel");r&&t(e.target).parents(r).length||n(e)})}function ve(){function e(e,n){var r=o.shift();return r||(r=t("
")),r[0].parentNode!=n[0]&&r.appendTo(n),a.push(r.css(e).show()),r}function n(){for(var t;t=a.shift();)o.push(t.hide().unbind())}var r=this;r.renderOverlay=e,r.clearOverlays=n;var a=[],o=[]}function he(t){var e,n,r=this;r.build=function(){e=[],n=[],t(e,n)},r.cell=function(t,r){var a,o=e.length,i=n.length,s=-1,c=-1;for(a=0;o>a;a++)if(r>=e[a][0]&&e[a][1]>r){s=a;break}for(a=0;i>a;a++)if(t>=n[a][0]&&n[a][1]>t){c=a;break}return s>=0&&c>=0?{row:s,col:c}:null},r.rect=function(t,r,a,o,i){var s=i.offset();return{top:e[t][0]-s.top,left:n[r][0]-s.left,width:n[o][1]-n[r][0],height:e[a][1]-e[t][0]}}}function ge(e){function n(t){pe(t);var n=e.cell(t.pageX,t.pageY);(!n!=!i||n&&(n.row!=i.row||n.col!=i.col))&&(n?(o||(o=n),a(n,o,n.row-o.row,n.col-o.col)):a(n,o),i=n)}var r,a,o,i,s=this;s.start=function(s,c,l){a=s,o=i=null,e.build(),n(c),r=l||"mousemove",t(document).bind(r,n)},s.stop=function(){return t(document).unbind(r,n),i}}function pe(t){t.pageX===e&&(t.pageX=t.originalEvent.pageX,t.pageY=t.originalEvent.pageY)}function me(t){function n(e){return a[e]=a[e]||t(e)}var r=this,a={},o={},i={};r.left=function(t){return o[t]=o[t]===e?n(t).position().left:o[t]},r.right=function(t){return i[t]=i[t]===e?r.left(t)+n(t).width():i[t]},r.clear=function(){a={},o={},i={}}}var ye={defaultView:"month",aspectRatio:1.35,header:{left:"title",center:"",right:"today prev,next"},weekends:!0,weekNumbers:!1,weekNumberCalculation:"iso",weekNumberTitle:"W",allDayDefault:!0,ignoreTimezone:!0,lazyFetching:!0,startParam:"start",endParam:"end",titleFormat:{month:"MMMM yyyy",week:"MMM d[ yyyy]{ '—'[ MMM] d yyyy}",day:"dddd, MMM d, yyyy"},columnFormat:{month:"ddd",week:"ddd M/d",day:"dddd M/d"},timeFormat:{"":"h(:mm)t"},isRTL:!1,firstDay:0,monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNamesShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayNamesShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],buttonText:{prev:"",next:"",prevYear:"«",nextYear:"»",today:"today",month:"month",week:"week",day:"day"},theme:!1,buttonIcons:{prev:"circle-triangle-w",next:"circle-triangle-e"},unselectAuto:!0,dropAccept:"*"},be={header:{left:"next,prev today",center:"",right:"title"},buttonText:{prev:"",next:"",prevYear:"»",nextYear:"«"},buttonIcons:{prev:"circle-triangle-e",next:"circle-triangle-w"}},we=t.fullCalendar={version:"1.6.1"},De=we.views={};t.fn.fullCalendar=function(n){if("string"==typeof n){var a,o=Array.prototype.slice.call(arguments,1);return this.each(function(){var r=t.data(this,"fullCalendar");if(r&&t.isFunction(r[n])){var i=r[n].apply(r,o);a===e&&(a=i),"destroy"==n&&t.removeData(this,"fullCalendar")}}),a!==e?a:this}var i=n.eventSources||[];return delete n.eventSources,n.events&&(i.push(n.events),delete n.events),n=t.extend(!0,{},ye,n.isRTL||n.isRTL===e&&ye.isRTL?be:{},n),this.each(function(e,a){var o=t(a),s=new r(o,n,i);o.data("fullCalendar",s),s.render()}),this},we.sourceNormalizers=[],we.sourceFetchers=[];var Me={dataType:"json",cache:!1},Ce=1;we.addDays=c,we.cloneDate=d,we.parseDate=m,we.parseISO8601=y,we.parseTime=b,we.formatDate=w,we.formatDates=D;var Se=["sun","mon","tue","wed","thu","fri","sat"],Ee=864e5,xe=36e5,Te=6e4,ke={s:function(t){return t.getSeconds()},ss:function(t){return P(t.getSeconds())},m:function(t){return t.getMinutes()},mm:function(t){return P(t.getMinutes())},h:function(t){return t.getHours()%12||12},hh:function(t){return P(t.getHours()%12||12)},H:function(t){return t.getHours()},HH:function(t){return P(t.getHours())},d:function(t){return t.getDate()},dd:function(t){return P(t.getDate())},ddd:function(t,e){return e.dayNamesShort[t.getDay()]},dddd:function(t,e){return e.dayNames[t.getDay()]},M:function(t){return t.getMonth()+1},MM:function(t){return P(t.getMonth()+1)},MMM:function(t,e){return e.monthNamesShort[t.getMonth()]},MMMM:function(t,e){return e.monthNames[t.getMonth()]},yy:function(t){return(t.getFullYear()+"").substring(2)},yyyy:function(t){return t.getFullYear()},t:function(t){return 12>t.getHours()?"a":"p"},tt:function(t){return 12>t.getHours()?"am":"pm"},T:function(t){return 12>t.getHours()?"A":"P"},TT:function(t){return 12>t.getHours()?"AM":"PM"},u:function(t){return w(t,"yyyy-MM-dd'T'HH:mm:ss'Z'")},S:function(t){var e=t.getDate();return e>10&&20>e?"th":["st","nd","rd"][e%10-1]||"th"},w:function(t,e){return e.weekNumberCalculation(t)},W:function(t){return M(t)}};we.dateFormatters=ke,we.applyAll=G,De.month=te,De.basicWeek=ee,De.basicDay=ne,n({weekMode:"fixed"}),De.agendaWeek=oe,De.agendaDay=ie,n({allDaySlot:!0,allDayText:"all-day",firstHour:6,slotMinutes:30,defaultEventMinutes:120,axisFormat:"h(:mm)tt",timeFormat:{agenda:"h:mm{ - h:mm}"},dragOpacity:{agenda:.5},minTime:0,maxTime:24})})(jQuery); \ No newline at end of file diff --git a/app/assets/stylesheets/calendar.css b/app/assets/stylesheets/calendar.css new file mode 100644 index 0000000..128559b --- /dev/null +++ b/app/assets/stylesheets/calendar.css @@ -0,0 +1,394 @@ +/* orbit calendar */ +#orbit_calendar { + padding: 10px 8px; + min-width: 960px; + transition: all 0.3s ease; + -webkit-transition: all 0.3s ease; + -moz-transition: all 0.3s ease; +} +.calendar_color_tag { + display: inline-block; + width: 18px; + height: 18px; + margin-right: 4px; + vertical-align: bottom; +} +.current_day_title { + text-align: center; + line-height: 28px; +} +.calendar_mode { + z-index: 2; +} +.mode_switch { + text-transform: capitalize; +} +.today { + background-color: #D9EDF7; +} +.event { + font-size: 12px; + border-radius: 3px; + cursor: pointer; + padding: 1px 3px; + font-weight: bold; + box-shadow: inset 0 0 1px black; + -webkit-box-shadow: inset 0 0 1px black; + -moz-box-shadow: inset 0 0 1px black; +} +.modal-body { + max-height: 515px; +} +.event_list_wrapper { + position: relative; +} +.event_list .cell { + height: 39px; + border: solid 1px #ddd; + border-top: 0; +} +.event_list .divide { + height: 19px; + margin-bottom: 18px; + border-bottom: solid 1px #eee; +} +.event_list .day_time { + height: 31px; + border-bottom: solid 1px #ddd; + border-left: solid 1px #ddd; + text-align: right; + padding: 4px; +} +.event dl, .event dt, .event dd { + margin: 0; + padding: 0; +} +.event dl { + padding: 3px; +} +.event dt { + font-size: 11px; + font-weight: normal; + line-height: 12px; +} +#calendar_day .event_holder { + width: 100%; + /*height: 100%;*/ + position: absolute; + top: 0; + z-index: 1; +} +.event_holder .inner { + position: relative; + margin: 0 16px 0 2px; +} +.event_holder .event { + padding: 0px; + position: absolute; + width: 100%; +} +.event_holder .event.half { + +} +.event_holder .event.over { + border: solid 1px #fff; +} +/* month view */ +#calendar_month { + border-bottom: solid 1px #ddd; +} +#calendar_month .month_row { + position: relative; + border: solid 1px #ddd; + border-bottom: 0; + height: 60px; + overflow: hidden; +} +#calendar_month .month_row .table { + table-layout: fixed; + margin-bottom: 0; + width: 100%; + position: absolute; +} +#calendar_month .month_row .table td { + border: 0; + border-left: solid 1px #ddd; + padding: 2px 4px 0 4px; +} +#calendar_month .month_row .table td:first-child { + border-left: 0; +} +#calendar_month .month_row.header { + height: 28px; + border: 0; +} +#calendar_month .month_row.header th { + font-size: 12px; + padding: 4px; + border: 0; +} +#calendar_month .month_row .month_table { + height: 100%; +} +#calendar_month .month_row .month_date { + color: #666; + font-size: 11px; + cursor: pointer; +} +#calendar_month .month_row .month_date td { + border-left: 0 +} +#calendar_month .month_row .month_date .day_title:hover { + text-decoration: underline; +} +#calendar_month .month_row td.today { + border-bottom: solid 1px #fff; + border-top: solid 1px #fff; +} +#calendar_month .month_row .event { + margin: 0 -2px; + position: relative; + color: #000; +} +#calendar_month .month_row .month_date .event:hover { + text-decoration: none !important; +} +#calendar_month .month_row td.disable { + background-color: #f6f6f6; + color: #ccc; + border-left: solid 1px #ddd; +} +#calendar_month .event.single { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} + +/* agenda view */ +#calendar_agenda { + margin-top: 20px; +} +#calendar_agenda .table { + margin-bottom: 0; +} +#calendar_agenda .tiny_calendar { + border: solid 1px #eee; +} +#calendar_agenda .tiny_calendar .table th { + border-top: 0; +} +#calendar_agenda .tiny_calendar .table td { + text-align: center; +} +#calendar_agenda .event { + -webkit-box-shadow: none; + -moz-box-shadow: none; + box-shadow: none; +} +#calendar_agenda .row-fluid { + margin-top: 20px; + padding-top: 20px; + border-top: dashed 1px #ddd; +} +#calendar_agenda .row-fluid:first-child { + border-top: 0; + padding-top: 0; + margin-top: 0; +} +#calendar_agenda .table.event_list .span2, #calendar_agenda .table.event_list thead th { + min-height: 0 !important; + height: 0 !important; + line-height: 0; + padding: 0; +} +.event_time { + font-family: Tahoma, sans-serif; +} +.has_event { + background-color: #08c; + color: #fff; +} + +/* day view */ +#calendar_day .header { + margin-bottom: 10px; +} +#calendar_day .header th { + text-align: center; +} +#calendar_day td { + border: 0; +} +#calendar_day .event { + margin-bottom: 2px; +} +#calendar_day .all_day_event { + background: #eee; + border: solid 1px #ddd; +} +#calendar_day .event_list .table { + border-top: solid 1px #ddd; +} +#calendar_day .event_list td { + padding: 0; +} + +/* week view */ +#calendar_week { + +} +#calendar_week .cell_wrapper { + position: absolute; + width: 100%; +} +#calendar_week td { + padding: 0; +} +#calendar_week .table { + margin-bottom: 0; +} +#calendar_week .header { + margin-bottom: 12px; + border-top: 0; + table-layout: fixed; +} +#calendar_week .header th { + text-align: center; + font-size: 12px; +} +#calendar_week .header td { + border: solid 1px #ddd; + /*background-color: #eee;*/ +} +#calendar_week .week_day { + padding: 0 2px; + border: solid 1px #ddd; +} +#calendar_week .header .week_day { + padding: 2px 4px 0px 2px; +} +#calendar_week .event_list .event { + position: absolute; + width: 100%; + margin-bottom: 2px; +} +#calendar_week .cell_map { + margin-bottom: 18px; +} +#calendar_week .cell_map td { + border-top: 0; + border-bottom: 0; +} +#calendar_week .cell_map tr:first-child td { + border-top: solid 1px #ddd; +} +#calendar_week .event_holder .inner { + margin: 0 8px 0 0; +} +#calendar_week .all_day_event_holder { + position: relative; + width: 100%; + table-layout: fixed; +} +#calendar_week .all_day_event_holder td { + border: 0; + background-color: transparent; +} +#calendar_week .all_day_event { + background: #eee; +} + + + +/* calendars(category) */ +.calendars_color_tag { + width: 20px; + height: 20px; + display: inline-block; + border-radius: 3px; + box-shadow: inset 0 -1px 0 rgba(0,0,0,0.2); +} + +/* Event Controller */ +.event_controller { + width: 350px; +} +.event_controller .row-fluid { + margin-bottom: 6px; +} +.event_controller .row-fluid .control-label { + line-height: 30px; +} + +.close { + border: 0; + background: none; +} + +/* miniColors tweak */ +.miniColors-trigger { + width: 20px; + height: 20px; + margin-bottom: 10px; + margin-left: 10px; + border-color: #f1f1f1; +} +.miniColors-selector { + float: none; + margin: 4px 0 0 0; +} + +/* category edit */ +.edit_cal { + margin: -8px; + background-color: whitesmoke; +} + +.edit_cal .table td, .edit_cal .table { + border: 0 !important; + background-color: transparent !important; + margin: 0 !important; +} +.main-list td { + border-top: solid 1px #ddd; +} + +/* create / edit event panel */ +#tags_panel { + top: auto; + bottom: 34px; + width: 258px; + height: 170px; + padding: 8px 0; + overflow: hidden; + position: absolute; + clear: none; +} +#tags_panel .viewport { + height: 170px; +} +#tags_panel .scrollbar { + top: 8px; +} +#tags_list { + padding: 8px; +} +.bootstrap-datetimepicker-widget.dropdown-menu { + z-index: 1051; +} +#main-wrap{ + padding-bottom: 0; +} +.fc-other-month{ + background-color: #F6F6F6; +} + +#calendar-loading{ + display: none; + background-color: red; + padding: 0 5px; + position: absolute; + top: 31px; + width: 60px; + z-index: 10; +} \ No newline at end of file diff --git a/app/assets/stylesheets/calendar/.gitkeep b/app/assets/stylesheets/calendar/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/stylesheets/cals.css b/app/assets/stylesheets/cals.css new file mode 100644 index 0000000..f07975b --- /dev/null +++ b/app/assets/stylesheets/cals.css @@ -0,0 +1,8 @@ +/* + *This is a manifest file that'll automatically include all the stylesheets available in this directory + *and any sub-directories. You're free to add application-wide styles to this file and they'll appear at + *the top of the compiled file, but it's generally better to create a new file per style scope. + *= font-awesome + *= calendar + *= bootstrap-responsive +*/ \ No newline at end of file diff --git a/app/assets/stylesheets/fullcalendar.css b/app/assets/stylesheets/fullcalendar.css new file mode 100644 index 0000000..c9add0b --- /dev/null +++ b/app/assets/stylesheets/fullcalendar.css @@ -0,0 +1,579 @@ +/*! + * FullCalendar v1.6.1 Stylesheet + * Docs & License: http://arshaw.com/fullcalendar/ + * (c) 2013 Adam Shaw + */ + + +.fc { + direction: ltr; + text-align: left; + } + +.fc table { + border-collapse: collapse; + border-spacing: 0; + } + +html .fc, +.fc table { + font-size: 1em; + } + +.fc td, +.fc th { + padding: 0; + vertical-align: top; + } + + + +/* Header +------------------------------------------------------------------------*/ + +.fc-header td { + white-space: nowrap; + } + +.fc-header-left { + width: 25%; + text-align: left; + } + +.fc-header-center { + text-align: center; + } + +.fc-header-right { + width: 25%; + text-align: right; + } + +.fc-header-title { + display: inline-block; + vertical-align: top; + } + +.fc-header-title h2 { + margin-top: 0; + white-space: nowrap; + } + +.fc .fc-header-space { + padding-left: 10px; + } + +.fc-header .fc-button { + margin-bottom: 1em; + vertical-align: top; + } + +/* buttons edges butting together */ + +.fc-header .fc-button { + margin-right: -1px; + } + +.fc-header .fc-corner-right, /* non-theme */ +.fc-header .ui-corner-right { /* theme */ + margin-right: 0; /* back to normal */ + } + +/* button layering (for border precedence) */ + +.fc-header .fc-state-hover, +.fc-header .ui-state-hover { + z-index: 2; + } + +.fc-header .fc-state-down { + z-index: 3; + } + +.fc-header .fc-state-active, +.fc-header .ui-state-active { + z-index: 4; + } + + + +/* Content +------------------------------------------------------------------------*/ + +.fc-content { + clear: both; + } + +.fc-view { + width: 100%; /* needed for view switching (when view is absolute) */ + overflow: hidden; + } + + + +/* Cell Styles +------------------------------------------------------------------------*/ + +.fc-widget-header, /*
, usually */ +.fc-widget-content { /* , usually */ + border: 1px solid #ddd; + } + +.fc-state-highlight { /* today cell */ /* TODO: add .fc-today to */ + background: #fcf8e3; + } + +.fc-cell-overlay { /* semi-transparent rectangle while dragging */ + background: #bce8f1; + opacity: .3; + filter: alpha(opacity=30); /* for IE */ + } + + + +/* Buttons +------------------------------------------------------------------------*/ + +.fc-button { + position: relative; + display: inline-block; + padding: 0 .6em; + overflow: hidden; + height: 1.9em; + line-height: 1.9em; + white-space: nowrap; + cursor: pointer; + } + +.fc-state-default { /* non-theme */ + border: 1px solid; + } + +.fc-state-default.fc-corner-left { /* non-theme */ + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + +.fc-state-default.fc-corner-right { /* non-theme */ + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } + +/* + Our default prev/next buttons use HTML entities like ‹ › « » + and we'll try to make them look good cross-browser. +*/ + +.fc-text-arrow { + margin: 0 .1em; + font-size: 2em; + font-family: "Courier New", Courier, monospace; + vertical-align: baseline; /* for IE7 */ + } + +.fc-button-prev .fc-text-arrow, +.fc-button-next .fc-text-arrow { /* for ‹ › */ + font-weight: bold; + } + +/* icon (for jquery ui) */ + +.fc-button .fc-icon-wrap { + position: relative; + float: left; + top: 50%; + } + +.fc-button .ui-icon { + position: relative; + float: left; + margin-top: -50%; + *margin-top: 0; + *top: -50%; + } + +/* + button states + borrowed from twitter bootstrap (http://twitter.github.com/bootstrap/) +*/ + +.fc-state-default { + background-color: #f5f5f5; + background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); + background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); + background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); + background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); + background-repeat: repeat-x; + border-color: #e6e6e6 #e6e6e6 #bfbfbf; + border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); + color: #333; + text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); + } + +.fc-state-hover, +.fc-state-down, +.fc-state-active, +.fc-state-disabled { + color: #333333; + background-color: #e6e6e6; + } + +.fc-state-hover { + color: #333333; + text-decoration: none; + background-position: 0 -15px; + -webkit-transition: background-position 0.1s linear; + -moz-transition: background-position 0.1s linear; + -o-transition: background-position 0.1s linear; + transition: background-position 0.1s linear; + } + +.fc-state-down, +.fc-state-active { + background-color: #cccccc; + background-image: none; + outline: 0; + box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); + } + +.fc-state-disabled { + cursor: default; + background-image: none; + opacity: 0.65; + filter: alpha(opacity=65); + box-shadow: none; + } + + + +/* Global Event Styles +------------------------------------------------------------------------*/ + +.fc-event { + border: 1px solid #3a87ad; /* default BORDER color */ + background-color: #3a87ad; /* default BACKGROUND color */ + color: #fff; /* default TEXT color */ + font-size: .85em; + cursor: default; + } + +a.fc-event { + text-decoration: none; + } + +a.fc-event, +.fc-event-draggable { + cursor: pointer; + } + +.fc-rtl .fc-event { + text-align: right; + } + +.fc-event-inner { + width: 100%; + height: 100%; + overflow: hidden; + } + +.fc-event-time, +.fc-event-title { + padding: 0 1px; + } + +.fc .ui-resizable-handle { + display: block; + position: absolute; + z-index: 99999; + overflow: hidden; /* hacky spaces (IE6/7) */ + font-size: 300%; /* */ + line-height: 50%; /* */ + } + + + +/* Horizontal Events +------------------------------------------------------------------------*/ + +.fc-event-hori { + border-width: 1px 0; + margin-bottom: 1px; + } + +.fc-ltr .fc-event-hori.fc-event-start, +.fc-rtl .fc-event-hori.fc-event-end { + border-left-width: 1px; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; + } + +.fc-ltr .fc-event-hori.fc-event-end, +.fc-rtl .fc-event-hori.fc-event-start { + border-right-width: 1px; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + } + +/* resizable */ + +.fc-event-hori .ui-resizable-e { + top: 0 !important; /* importants override pre jquery ui 1.7 styles */ + right: -3px !important; + width: 7px !important; + height: 100% !important; + cursor: e-resize; + } + +.fc-event-hori .ui-resizable-w { + top: 0 !important; + left: -3px !important; + width: 7px !important; + height: 100% !important; + cursor: w-resize; + } + +.fc-event-hori .ui-resizable-handle { + _padding-bottom: 14px; /* IE6 had 0 height */ + } + + + +/* Reusable Separate-border Table +------------------------------------------------------------*/ + +table.fc-border-separate { + border-collapse: separate; + } + +.fc-border-separate th, +.fc-border-separate td { + border-width: 1px 0 0 1px; + } + +.fc-border-separate th.fc-last, +.fc-border-separate td.fc-last { + border-right-width: 1px; + } + +.fc-border-separate tr.fc-last th, +.fc-border-separate tr.fc-last td { + border-bottom-width: 1px; + } + +.fc-border-separate tbody tr.fc-first td, +.fc-border-separate tbody tr.fc-first th { + border-top-width: 0; + } + + + +/* Month View, Basic Week View, Basic Day View +------------------------------------------------------------------------*/ + +.fc-grid th { + text-align: center; + } + +.fc .fc-week-number { + width: 22px; + text-align: center; + } + +.fc .fc-week-number div { + padding: 0 2px; + } + +.fc-grid .fc-day-number { + float: right; + padding: 0 2px; + } + +.fc-grid .fc-other-month .fc-day-number { + opacity: 0.3; + filter: alpha(opacity=30); /* for IE */ + /* opacity with small font can sometimes look too faded + might want to set the 'color' property instead + making day-numbers bold also fixes the problem */ + } + +.fc-grid .fc-day-content { + clear: both; + padding: 2px 2px 1px; /* distance between events and day edges */ + } + +/* event styles */ + +.fc-grid .fc-event-time { + font-weight: bold; + } + +/* right-to-left */ + +.fc-rtl .fc-grid .fc-day-number { + float: left; + } + +.fc-rtl .fc-grid .fc-event-time { + float: right; + } + + + +/* Agenda Week View, Agenda Day View +------------------------------------------------------------------------*/ + +.fc-agenda table { + border-collapse: separate; + } + +.fc-agenda-days th { + text-align: center; + } + +.fc-agenda .fc-agenda-axis { + width: 50px; + padding: 0 4px; + vertical-align: middle; + text-align: right; + white-space: nowrap; + font-weight: normal; + } + +.fc-agenda .fc-week-number { + font-weight: bold; + } + +.fc-agenda .fc-day-content { + padding: 2px 2px 1px; + } + +/* make axis border take precedence */ + +.fc-agenda-days .fc-agenda-axis { + border-right-width: 1px; + } + +.fc-agenda-days .fc-col0 { + border-left-width: 0; + } + +/* all-day area */ + +.fc-agenda-allday th { + border-width: 0 1px; + } + +.fc-agenda-allday .fc-day-content { + /*min-height: 34px; *//* TODO: doesnt work well in quirksmode */ + _height: 34px; + } + +/* divider (between all-day and slots) */ + +.fc-agenda-divider-inner { + height: 2px; + overflow: hidden; + } + +.fc-widget-header .fc-agenda-divider-inner { + background: #eee; + } + +/* slot rows */ + +.fc-agenda-slots th { + border-width: 1px 1px 0; + } + +.fc-agenda-slots td { + border-width: 1px 0 0; + background: none; + } + +.fc-agenda-slots td div { + height: 20px; + } + +.fc-agenda-slots tr.fc-slot0 th, +.fc-agenda-slots tr.fc-slot0 td { + border-top-width: 0; + } + +.fc-agenda-slots tr.fc-minor th, +.fc-agenda-slots tr.fc-minor td { + border-top-style: dotted; + } + +.fc-agenda-slots tr.fc-minor th.ui-widget-header { + *border-top-style: solid; /* doesn't work with background in IE6/7 */ + } + + + +/* Vertical Events +------------------------------------------------------------------------*/ + +.fc-event-vert { + border-width: 0 1px; + } + +.fc-event-vert.fc-event-start { + border-top-width: 1px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + } + +.fc-event-vert.fc-event-end { + border-bottom-width: 1px; + border-bottom-left-radius: 3px; + border-bottom-right-radius: 3px; + } + +.fc-event-vert .fc-event-time { + white-space: nowrap; + font-size: 10px; + } + +.fc-event-vert .fc-event-inner { + position: relative; + z-index: 2; + } + +.fc-event-vert .fc-event-bg { /* makes the event lighter w/ a semi-transparent overlay */ + position: absolute; + z-index: 1; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #fff; + opacity: .25; + filter: alpha(opacity=25); + } + +.fc .ui-draggable-dragging .fc-event-bg, /* TODO: something nicer like .fc-opacity */ +.fc-select-helper .fc-event-bg { + display: none\9; /* for IE6/7/8. nested opacity filters while dragging don't work */ + } + +/* resizable */ + +.fc-event-vert .ui-resizable-s { + bottom: 0 !important; /* importants override pre jquery ui 1.7 styles */ + width: 100% !important; + height: 8px !important; + overflow: hidden !important; + line-height: 8px !important; + font-size: 11px !important; + font-family: monospace; + text-align: center; + cursor: s-resize; + } + +.fc-agenda .ui-resizable-resizing { /* TODO: better selector */ + _overflow: hidden; + } + + diff --git a/app/assets/stylesheets/fullcalendar.print.css b/app/assets/stylesheets/fullcalendar.print.css new file mode 100644 index 0000000..227b80e --- /dev/null +++ b/app/assets/stylesheets/fullcalendar.print.css @@ -0,0 +1,61 @@ +/* + * FullCalendar v1.5.4 Print Stylesheet + * + * Include this stylesheet on your page to get a more printer-friendly calendar. + * When including this stylesheet, use the media='print' attribute of the tag. + * Make sure to include this stylesheet IN ADDITION to the regular fullcalendar.css. + * + * Copyright (c) 2011 Adam Shaw + * Dual licensed under the MIT and GPL licenses, located in + * MIT-LICENSE.txt and GPL-LICENSE.txt respectively. + * + * Date: Tue Sep 4 23:38:33 2012 -0700 + * + */ + + + /* Events +-----------------------------------------------------*/ + +.fc-event-skin { + background: none !important; + color: #000 !important; + } + +/* horizontal events */ + +.fc-event-hori { + border-width: 0 0 1px 0 !important; + border-bottom-style: dotted !important; + border-bottom-color: #000 !important; + padding: 1px 0 0 0 !important; + } + +.fc-event-hori .fc-event-inner { + border-width: 0 !important; + padding: 0 1px !important; + } + +/* vertical events */ + +.fc-event-vert { + border-width: 0 0 0 1px !important; + border-left-style: dotted !important; + border-left-color: #000 !important; + padding: 0 1px 0 0 !important; + } + +.fc-event-vert .fc-event-inner { + border-width: 0 !important; + padding: 1px 0 !important; + } + +.fc-event-bg { + display: none !important; + } + +.fc-event .ui-resizable-handle { + display: none !important; + } + + diff --git a/app/controllers/.gitkeep b/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/admin/calendar_types_controller.rb b/app/controllers/admin/calendar_types_controller.rb new file mode 100644 index 0000000..cd92c2c --- /dev/null +++ b/app/controllers/admin/calendar_types_controller.rb @@ -0,0 +1,49 @@ +class Admin::CalendarTypesController < OrbitAdminController + # include AdminHelper + + # GET /events + # GET /events.json + + def initialize + super + @app_title = "calendar" + end + + def index + @calendars = CalendarType.all rescue [] + @calendar_type = CalendarType.new + respond_to do |format| + format.html # index.html.erb + format.json { render json: @calendars.to_json } + end + end + + def new + @calendar = CalendarType.new + end + + def create + calendar = CalendarType.new + category = Category.new + category.title_translations = calendar_type_params["title_translations"] + category.module_app_id = @module_app.id + category.save + calendar.update_attributes(calendar_type_params) + calendar.category = category + calendar.save + redirect_to admin_calendar_types_path + end + + def list + @module_app_id = @module_app.id rescue nil + + @calendars = CalendarType.all rescue [] + render :layout => false + end + + private + def calendar_type_params + params.require(:calendar_type).permit! + end + +end \ No newline at end of file diff --git a/app/controllers/admin/calendars_controller.rb b/app/controllers/admin/calendars_controller.rb new file mode 100644 index 0000000..c65ee68 --- /dev/null +++ b/app/controllers/admin/calendars_controller.rb @@ -0,0 +1,148 @@ +class Admin::CalendarsController < OrbitAdminController + # GET /events + # GET /events.json + + def index + + if params[:start].present? && params[:end].present? + sdt = Time.at(params[:start].to_i) + edt = Time.at(params[:end].to_i) + @monthly_events = Event.monthly_event(sdt,edt) + @re = Event.recurring_event(sdt,edt) + events = @monthly_events.inject(@re, :<<) + end + respond_to do |format| + format.html # index.html.erb + format.json { render json: events.to_json } + end + end + + # GET /events/1 + # GET /events/1.json + def show + @event = Event.find(params[:id]) + respond_to do |format| + format.html # show.html.erb + format.json { render json: @event } + end + end + + def agenda + agenda_start = Date.strptime(params[:agenda_start], '%m/%d/%Y') + agenda_end = Date.strptime(params[:agenda_end], '%m/%d/%Y') + @events = Event.agenda_events(agenda_start,agenda_end) + # re = Event.recurring_event(Time.at(params[:unix_start].to_i), Time.at(params[:unix_end].to_i)) + # @events = @events.inject(re, :<<) + render :json=>@events.to_json + + end + + # GET /events/new + # GET /events/new.json + def new + @event = Event.new + categories = user_authenticated_categories rescue [] + if categories.first == "all" + @categories = CalendarType.all + else + @categories = CalendarType.where(:category_id.in => categories) rescue [] + end + @end_d_t = params[:endDate] + @start_d_t = params[:startDate] + @all_day = false; + @recurring = false; + if params + case params[:allDay] + when "true" + @all_day = true + when "false" + @all_day = false + case params[:recurring] + when "true" + @recurring = true + when "false" + @recurring = false + end + end + end + render :layout => false + + end + + # GET /events/1/edit + def edit + @event = Event.find(params[:id]) + categories = user_authenticated_categories rescue [] + if categories.first == "all" + @categories = CalendarType.all + else + @categories = CalendarType.where(:category_id.in => categories) rescue [] + end + @end_d_t = @event.end.strftime("%Y/%m/%d %H:%M").to_s + @start_d_t = @event.start.strftime("%Y/%m/%d %H:%M").to_s + @all_day = @event.all_day + @recurring = @event.recurring + render :layout => false + end + + # POST /events + # POST /events.json + def create + @event = Event.new(event_page_params) + + if @event.present? && @event.save + render json: @event.to_json + else + respond_to do |format| + format.html { render action: "new" } + format.json { render json: @event.errors, status: :unprocessable_entity } + end + end + end + + # PUT /events/1 + # PUT /events/1.json + def update + @event = Event.find(params[:id]) + if @event.update_attributes(event_page_params) + render json: @event.to_json + else + respond_to do |format| + format.html { render action: "edit" } + format.json { render json: @event.errors, status: :unprocessable_entity } + #format.js + end + end + end + + # DELETE /events/1 + # DELETE /events/1.json + def destroy + @event = Event.find(params[:id]) + @event.destroy + render :json => {"success"=>true}.to_json + # respond_to do |format| + # format.html { redirect_to events } + # format.json { head :no_content } + # end + end + + private + def event_page_params + params.require(:event).permit! + end + + def can_edit_or_delete_event?(obj) + create_user = obj.create_user_id.to_s rescue nil + if @user_authenticated_categories.first == "all" + return true + elsif @current_user_is_sub_manager && !create_user.nil? + create_user == current_user.id.to_s + else + @user_authenticated_categories.include?obj.calendar_type.category_id rescue (current_user.is_manager?(@module_app) rescue false) + end + end + +end + + diff --git a/app/controllers/calendars_controller.rb b/app/controllers/calendars_controller.rb new file mode 100644 index 0000000..e0d412e --- /dev/null +++ b/app/controllers/calendars_controller.rb @@ -0,0 +1,56 @@ +class CalendarsController < ApplicationController + # GET /events + # GET /events.json + + + def index + { + "extras" => { + "page_id" => OrbitHelper.params[:page_id] + } + } + end + + def events + page = Page.find_by(:page_id => params[:page_id]) rescue nil + events =[] + if !page.nil? + categories = page.categories + if categories.first == "all" + calendar_types = CalendarType.all.collect{|ct| ct.id.to_s } + else + calendar_types = CalendarType.where(:category_id.in => categories).collect{|ct| ct.id.to_s } rescue [] + end + if params[:start].present? && params[:end].present? + sdt = Time.at(params[:start].to_i) + edt = Time.at(params[:end].to_i) + events = Event.monthly_event(sdt,edt).where(:calendar_type_id.in => calendar_types) + end + end + respond_to do |format| + format.html # index.html.erb + format.json { render json: events.to_json } + end + end + + def agenda + # re = Event.recurring_event(Time.at(params[:unix_start].to_i), Time.at(params[:unix_end].to_i)) + # @events = @events.inject(re, :<<) + page = Page.find_by(:page_id => params[:page_id]) rescue nil + events =[] + if !page.nil? + categories = page.categories + if categories.first == "all" + calendar_types = CalendarType.all.collect{|ct| ct.id.to_s } + else + calendar_types = CalendarType.where(:category_id.in => categories).collect{|ct| ct.id.to_s } rescue [] + end + if params[:agenda_start].present? && params[:agenda_end].present? + agenda_start = Date.strptime(params[:agenda_start], '%m/%d/%Y') + agenda_end = Date.strptime(params[:agenda_end], '%m/%d/%Y') + events = Event.agenda_events(agenda_start,agenda_end).where(:calendar_type_id.in => calendar_types) + end + end + render json: events.to_json + end +end \ No newline at end of file diff --git a/app/helpers/.gitkeep b/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/mailers/.gitkeep b/app/mailers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/.gitkeep b/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/calendar_type.rb b/app/models/calendar_type.rb new file mode 100644 index 0000000..48a080b --- /dev/null +++ b/app/models/calendar_type.rb @@ -0,0 +1,10 @@ +class CalendarType + include Mongoid::Document + include Mongoid::Timestamps + include OrbitCategory::Categorizable + + field :title, localize: true + field :color + + has_many :events, :dependent => :destroy +end \ No newline at end of file diff --git a/app/models/event.rb b/app/models/event.rb new file mode 100644 index 0000000..3476b36 --- /dev/null +++ b/app/models/event.rb @@ -0,0 +1,115 @@ +require 'time' +require 'date' + +class Event + include Mongoid::Document + include Mongoid::Timestamps + include OrbitTag::Taggable + # include Mongoid::MultiParameterAttributes + + field :title + field :note + field :start, type: DateTime + field :end, type: DateTime + field :all_day, type: Boolean + field :recurring, type: Boolean + field :frequency + field :period + + belongs_to :calendar_type + attr_accessor :agenda_start, :agenda_end, :get_agenda_events + + + validates_presence_of :title, :message => "Please fill the title of the Event" + + def self.format_date(date_time) + Time.at(date_time.to_i).to_formatted_s(:db) + end + + REPEATS = [ + "Daily" , + "Weekly" , + "Monthly" , + "Yearly" + ] + + def as_json(options = {}) + { + :id => self.id.to_s, + :title => self.title, + :note => self.note || "", + :start => self.start.rfc822, + :end => self.end.rfc822, + :allDay => self.all_day, + :recurring => self.recurring, + :calendar => self.calendar_type_id.to_s, + :color => (self.calendar_type.color rescue nil), + :edit_url => Rails.application.routes.url_helpers.edit_admin_calendar_path(:locale=>I18n.locale, :id=>self.id), + :delete_url => Rails.application.routes.url_helpers.admin_calendar_path(:locale=>I18n.locale, :id=>self.id) + } + + end + + def self.monthly_event(start_date,end_date) + self.any_of(:start.gte => start_date, :end.gte => start_date).and(:start.lte => end_date).asc(:start) + end + + def self.recurring_event(start_date,end_date) + @recurring_events = self.where(:recurring => true) + @recurring = [] + @recurring_events.each do |re| + case re.period + when 'Daily' + if (start_date..end_date).cover?(re.start) + @i = TimeDifference.between(re.start,end_date).in_days.to_i + (1..@i).each do |i| + @start_date = re.start + i + @recurring << {:id => re.id.to_s, :title=>re.title, :note=>re.note, :start=>@start_date, :end => @start_date, :allDay => re.all_day, :recurring => re.recurring, :calendar => re.calendar_type.id.to_s, :color => re.calendar_type.color, :edit_url => Rails.application.routes.url_helpers.edit_admin_calendar_path(:locale=>I18n.locale, :id=>re.id), :delete_url => Rails.application.routes.url_helpers.admin_calendar_path(:locale=>I18n.locale, :id=>re.id)} + end + elsif re.start < start_date + @i = TimeDifference.between(start_date,end_date).in_days.to_i + (0..@i-1).each do |i| + @start_date = start_date.to_date + i + @recurring << {:id => re.id.to_s, :title=>re.title, :note=>re.note, :start=>@start_date, :end => @start_date, :allDay => re.all_day, :recurring => re.recurring, :calendar => re.calendar_type.id.to_s, :color => re.calendar_type.color, :edit_url => Rails.application.routes.url_helpers.edit_admin_calendar_path(:locale=>I18n.locale, :id=>re.id), :delete_url => Rails.application.routes.url_helpers.admin_calendar_path(:locale=>I18n.locale, :id=>re.id)} + end + end + when "Weekly" + @start_date = re.start + @end_date = re.end + @i = TimeDifference.between(re.start,end_date).in_weeks.to_i + (1..@i).each do |i| + @start_date += (7*re.frequency.to_i) + @end_date += (7*re.frequency.to_i) + + @recurring << {:id => re.id.to_s, :title=>re.title, :note=>re.note, :start=>@start_date, :end => @end_date, :allDay => re.all_day, :recurring => re.recurring, :calendar => re.calendar_type.id.to_s, :color => re.calendar_type.color, :edit_url => Rails.application.routes.url_helpers.edit_admin_calendar_path(:locale=>I18n.locale, :id=>re.id), :delete_url => Rails.application.routes.url_helpers.admin_calendar_path(:locale=>I18n.locale, :id=>re.id)} + end + when "Monthly" + if !(start_date..end_date).cover?(re.start) + sd = re.start + ed = re.end + @i = TimeDifference.between(re.start,end_date).in_months.to_i + @start_date = sd + sd = sd >> @i*re.frequency.to_i + ed = ed >> @i*re.frequency.to_i + @recurring << {:id => re.id.to_s, :title=>re.title, :note=>re.note, :start=>sd, :end => ed, :allDay => re.all_day, :recurring => re.recurring, :calendar => re.calendar_type.id.to_s, :color => re.calendar_type.color, :edit_url => Rails.application.routes.url_helpers.edit_admin_calendar_path(:locale=>I18n.locale, :id=>re.id), :delete_url => Rails.application.routes.url_helpers.admin_calendar_path(:locale=>I18n.locale, :id=>re.id)} + end + when "Yearly" + if !(start_date..end_date).cover?(re.start) + sd = re.start + ed = re.end + @i = TimeDifference.between(re.start,end_date).in_years.to_i + @start_date = sd + sd = sd >> 12 * @i*re.frequency.to_i + ed = ed >> 12 * @i*re.frequency.to_i + @recurring << {:id => re.id.to_s, :title=>re.title, :note=>re.note, :start=>sd, :end => ed, :allDay => re.all_day, :recurring => re.recurring, :calendar => re.calendar_type.id.to_s, :color => re.calendar_type.color, :edit_url => Rails.application.routes.url_helpers.edit_admin_calendar_path(:locale=>I18n.locale, :id=>re.id), :delete_url => Rails.application.routes.url_helpers.admin_calendar_path(:locale=>I18n.locale, :id=>re.id)} + end + end + end + + @recurring + end + + def self.agenda_events(agenda_start, agenda_end) + @all_events = self.any_of(:start.gte => agenda_start, :end.gte => agenda_start).and(:start.lte => agenda_end).asc(:start) + end +end \ No newline at end of file diff --git a/app/views/.gitkeep b/app/views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/views/admin/calendar_types/_calendar_type.html.erb b/app/views/admin/calendar_types/_calendar_type.html.erb new file mode 100644 index 0000000..06277f9 --- /dev/null +++ b/app/views/admin/calendar_types/_calendar_type.html.erb @@ -0,0 +1,13 @@ +
+ <%= calendar_type.title %> +
+ +
+
<%= calendar_type.color %>
+ + + + + + + + <%= render partial: 'calendar_type', collection: @calendars %> + +
Calendar<%= t('event_category.color') %>
+ +
+ + +
+
+
+
+ + + + +
+
+
+
+ <%= form_for @calendar_type, url: admin_calendar_types_path do |f| %> +
+ <%= render :partial => "form", :locals => { :f => f } %> +
+ <%= t(:cancel) %> + <%= f.submit t(:submit), class: 'btn btn-primary btn-small' %> +
+
+ <% end %> +
+
+
+
+ \ No newline at end of file diff --git a/app/views/admin/calendar_types/list.html.erb b/app/views/admin/calendar_types/list.html.erb new file mode 100644 index 0000000..6eb7f56 --- /dev/null +++ b/app/views/admin/calendar_types/list.html.erb @@ -0,0 +1 @@ + <%= render partial: 'calendar_type', collection: @calendars %> \ No newline at end of file diff --git a/app/views/admin/calendar_types/new.html.erb b/app/views/admin/calendar_types/new.html.erb new file mode 100644 index 0000000..afca95f --- /dev/null +++ b/app/views/admin/calendar_types/new.html.erb @@ -0,0 +1,35 @@ +<%= stylesheet_link_tag "jquery.miniColors" %> +<%= javascript_include_tag "jquery.miniColors.min" %> +
+<%= form_for @calendar, :url=> new_admin_calendar_type_path(@calendar) do |f| %> +

Add

+
+
+ <%= label_tag("color", t("calendar.color")) %> + <%= f.text_field :color, :class => "color-picker miniColors span5", :size => "7", :maxlength => "7", :autocomplete=>"off",:value=>"9100FF" %> +
+
+ +
+ <%= f.fields_for :name_translations do |name| %> + <% @site_valid_locales.each_with_index do |locale, i| %> +
+ <%= label_tag(locale, t("calendar.name")+"-"+ t(locale)) %> +
+ <%= name.text_field locale, :class => "input-xxlarge", :size=>"30" %> +
+
+ <% end %> + <% end %> +
+
+ <%= f.submit t("calendar.save"), :class=>"btn btn-primary" %> +
+ +<% end %> +
+ \ No newline at end of file diff --git a/app/views/admin/calendar_types/show.html.erb b/app/views/admin/calendar_types/show.html.erb new file mode 100644 index 0000000..4af386e --- /dev/null +++ b/app/views/admin/calendar_types/show.html.erb @@ -0,0 +1,3 @@ +<%=@event_category.name%> + +<%=@event_category.color%> \ No newline at end of file diff --git a/app/views/admin/calendar_types/update.js.erb b/app/views/admin/calendar_types/update.js.erb new file mode 100644 index 0000000..8875d21 --- /dev/null +++ b/app/views/admin/calendar_types/update.js.erb @@ -0,0 +1,15 @@ +$("#event_create").empty().hide(); +$("#create_event_btn").removeClass("active"); +$(".destroy").remove(); +$("#create_event_btn").show(); +switch (c.view){ + case "week": + c.loadWeekView(c.cur_week,c.cur_year); + break; + case "month": + c.loadMonthView(c.cur_month,c.cur_year); + break; + case "day": + c.loadDayView(c.cur_date,c.cur_month,c.cur_year); + break; +} diff --git a/app/views/admin/calendars/_form.html.erb b/app/views/admin/calendars/_form.html.erb new file mode 100644 index 0000000..9185aa9 --- /dev/null +++ b/app/views/admin/calendars/_form.html.erb @@ -0,0 +1,80 @@ + + <% if @event.errors.any? %> +
+

<%= pluralize(@event.errors.count, "error") %> prohibited this event from being saved:

+ +
    + <% @event.errors.full_messages.each do |msg| %> +
  • <%= msg %>
  • + <% end %> +
+
+ <% end %> +
+ <%= f.label :title, :class=>"control-label span3" %> + <%= f.text_field :title %> +
+
+ <%= f.label :note, :class=>"control-label span3" %> + <%= f.text_area :note, :rows => 3 %> +
+
+ +
+ +
+ <%= f.label :start, :class=>"control-label span3" %> + <%#= f.datetime_select :start %> +
+ <%= f.text_field :start, :class => "input-large", :placeholder => 'YYYY/MM/DD',:value => @start_d_t %> + + + + + + +
+
+
+ <%= f.label :end, :class=>"control-label span3" %> + <%#= f.datetime_select :end %> +
+ <%= f.text_field :end, :class => "input-large", :placeholder => 'YYYY/MM/DD', :value => @end_d_t %> + + + + + + + + +
+
+
+ <%= f.label "Calendar", :class=>"control-label span3" %> + <%= f.select :calendar_type_id, @categories.collect{|t| [ t.title, t.id ]} %> +
+
+ +
+
> +
+ <%=f.label :period, "Repeats",:class=>"control-label span3" %> + <%=f.select :period, Event::REPEATS,{},:class=>"span5" %> +
+
+ <%=f.label :frequency, "Every",:class=>"control-label span3" %> + <%=f.select :frequency, (1..30).to_a,{},:class=>"span2" %> +
+
+ +
+
+ <% if action_name == "edit" %> + <%= f.submit t("calendar.save"), :class=>"btn btn-primary" %> + <% else %> + <%= f.submit t(:create_), :class=>"btn btn-primary" %> + <% end %> + Cancel +
+
diff --git a/app/views/admin/calendars/create.js.erb b/app/views/admin/calendars/create.js.erb new file mode 100644 index 0000000..a07068c --- /dev/null +++ b/app/views/admin/calendars/create.js.erb @@ -0,0 +1,14 @@ +$("#event_create").empty().hide(); +$("#create_event_btn").removeClass("active"); +$(".destroy").remove(); +switch (c.view){ + case "week": + c.loadWeekView(c.cur_week,c.cur_year); + break; + case "month": + c.loadMonthView(c.cur_month,c.cur_year); + break; + case "day": + c.loadDayView(c.cur_date,c.cur_month,c.cur_year); + break; +} diff --git a/app/views/admin/calendars/edit.html.erb b/app/views/admin/calendars/edit.html.erb new file mode 100644 index 0000000..5125ce1 --- /dev/null +++ b/app/views/admin/calendars/edit.html.erb @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/app/views/admin/calendars/index.html.erb b/app/views/admin/calendars/index.html.erb new file mode 100644 index 0000000..069d31b --- /dev/null +++ b/app/views/admin/calendars/index.html.erb @@ -0,0 +1,69 @@ + +<%= javascript_include_tag 'fullcalendar' %> +<%= javascript_include_tag 'calendar' %> +<%= javascript_include_tag 'bootstrap-datetimepicker' %> + +<%= stylesheet_link_tag "fullcalendar"%> +<%= stylesheet_link_tag "calendar"%> + +
+
+
+ + +
+
+

+
+
+
+
+ + + + +
+
+ +
+
+
+
+
+
+
+
+
+ + +
Loading...
+
+
+
+
+
+ <%= link_to "Add", new_admin_calendar_path, :class => "btn btn-primary pull-right", :id=>"create_event_btn", :ref=>"add-btn" %> +
+
+
+ + \ No newline at end of file diff --git a/app/views/admin/calendars/new.html.erb b/app/views/admin/calendars/new.html.erb new file mode 100644 index 0000000..0a7b124 --- /dev/null +++ b/app/views/admin/calendars/new.html.erb @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/app/views/admin/calendars/show.html.erb b/app/views/admin/calendars/show.html.erb new file mode 100644 index 0000000..55f6520 --- /dev/null +++ b/app/views/admin/calendars/show.html.erb @@ -0,0 +1,20 @@ + + +<%if can_edit_or_delete?(@event.calendar_type) %> + +<% end %> diff --git a/app/views/admin/calendars/update.js.erb b/app/views/admin/calendars/update.js.erb new file mode 100644 index 0000000..8875d21 --- /dev/null +++ b/app/views/admin/calendars/update.js.erb @@ -0,0 +1,15 @@ +$("#event_create").empty().hide(); +$("#create_event_btn").removeClass("active"); +$(".destroy").remove(); +$("#create_event_btn").show(); +switch (c.view){ + case "week": + c.loadWeekView(c.cur_week,c.cur_year); + break; + case "month": + c.loadMonthView(c.cur_month,c.cur_year); + break; + case "day": + c.loadDayView(c.cur_date,c.cur_month,c.cur_year); + break; +} diff --git a/app/views/calendars/index.html.erb b/app/views/calendars/index.html.erb new file mode 100644 index 0000000..20f0461 --- /dev/null +++ b/app/views/calendars/index.html.erb @@ -0,0 +1,7 @@ + +<%= javascript_include_tag 'fullcalendar' %> +<%= javascript_include_tag 'calendar_frontend' %> +<%= stylesheet_link_tag "fullcalendar"%> +<%= stylesheet_link_tag "calendar"%> + +<%= render_view %> \ No newline at end of file diff --git a/calendar.gemspec b/calendar.gemspec new file mode 100644 index 0000000..5bd4048 --- /dev/null +++ b/calendar.gemspec @@ -0,0 +1,19 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "calendar/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "calendar" + s.version = Calendar::VERSION + s.authors = ["Harry Bomrah, Saurabh Bhatia"] + s.email = ["harry@rulingcom.com, saurabh@rulingcom.com"] + s.homepage = "http://www.rulingcom.com" + s.summary = "Calendar Module for Orbit." + s.description = "A module to display events on a Calendar" + + s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile", "README.rdoc"] + s.test_files = Dir["test/**/*"] + +end diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..52e9b16 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,11 @@ +en: + calendar: + all: All + calendar: Calendar + calendars: Calendars + color: Color + name: Name + save: Save + select_calendar: "Select Calendar" + categories: Categories + new_category: New Category diff --git a/config/locales/zh_tw.yml b/config/locales/zh_tw.yml new file mode 100644 index 0000000..7a4217e --- /dev/null +++ b/config/locales/zh_tw.yml @@ -0,0 +1,9 @@ +zh_tw: + calendar: + calendar: 行事曆 + calendars: 我的行事曆 + color: 顏色 + name: 名稱 + save: 儲存 + select_calendar: 選取行事曆 + categories: 類別 \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..697eb60 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,17 @@ +Rails.application.routes.draw do + + locales = Site.first.in_use_locales rescue I18n.available_locales + + scope "(:locale)", locale: Regexp.new(locales.join("|")) do + namespace :admin do + get "/calendars/agenda" => "calendars#agenda" + resources :calendars + resources :calendar_types + end + + get "/xhr/calendars/events" => "calendars#events" + get "/xhr/calendars/agenda" => "calendars#agenda" + end + +end + diff --git a/lib/calendar.rb b/lib/calendar.rb new file mode 100644 index 0000000..d6c39ef --- /dev/null +++ b/lib/calendar.rb @@ -0,0 +1,4 @@ +require "calendar/engine" + +module Calendar +end diff --git a/lib/calendar/engine.rb b/lib/calendar/engine.rb new file mode 100644 index 0000000..e30b826 --- /dev/null +++ b/lib/calendar/engine.rb @@ -0,0 +1,43 @@ +module Calendar + class Engine < ::Rails::Engine + initializer "calendar" do + OrbitApp.registration "Calendar", :type => "ModuleApp" do + module_label "calendar.calendar" + base_url File.expand_path File.dirname(__FILE__) + widget_methods ["widget"] + widget_settings [{"data_count"=>10}] + taggable "Event" + categorizable + authorizable + frontend_enabled + data_count 1..10 + + side_bar do + head_label_i18n 'calendar.calendar', icon_class: "icons-calendar" + available_for "users" + active_for_controllers (['admin/calendars','admin/calendar_types']) + head_link_path "admin_calendars_path" + + context_link 'calendar.calendar', + :link_path=>"admin_calendars_path" , + :priority=>1, + :active_for_action=>{'admin/calendars'=>'index'}, + :available_for => 'users' + context_link 'new_', + :link_path=>"admin_calendar_types_path" , + :priority=>2, + :active_for_action=>{'admin/calendar_types'=>'index'}, + :available_for => 'managers' + context_link 'tags', + :link_path=>"admin_module_app_tags_path" , + :link_arg=>"{:module_app_id=>ModuleApp.find_by(:key=>'calendar').id}", + :priority=>4, + :active_for_action=>{'admin/events'=>'tags'}, + :active_for_tag => 'Events', + :available_for => 'managers' + end + + end + end + end +end \ No newline at end of file diff --git a/lib/calendar/version.rb b/lib/calendar/version.rb new file mode 100644 index 0000000..820606d --- /dev/null +++ b/lib/calendar/version.rb @@ -0,0 +1,3 @@ +module Calendar + VERSION = "0.0.1" +end diff --git a/lib/tasks/calendar_tasks.rake b/lib/tasks/calendar_tasks.rake new file mode 100644 index 0000000..f5f67ef --- /dev/null +++ b/lib/tasks/calendar_tasks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :calendar do +# # Task goes here +# end diff --git a/script/rails b/script/rails new file mode 100755 index 0000000..3482193 --- /dev/null +++ b/script/rails @@ -0,0 +1,8 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +ENGINE_ROOT = File.expand_path('../..', __FILE__) +ENGINE_PATH = File.expand_path('../../lib/calendar/engine', __FILE__) + +require 'rails/all' +require 'rails/engine/commands' diff --git a/test/calendar_test.rb b/test/calendar_test.rb new file mode 100644 index 0000000..fe53cb4 --- /dev/null +++ b/test/calendar_test.rb @@ -0,0 +1,7 @@ +require 'test_helper' + +class CalendarTest < ActiveSupport::TestCase + test "truth" do + assert_kind_of Module, Calendar + end +end diff --git a/test/dummy/README.rdoc b/test/dummy/README.rdoc new file mode 100644 index 0000000..3e1c15c --- /dev/null +++ b/test/dummy/README.rdoc @@ -0,0 +1,261 @@ +== Welcome to Rails + +Rails is a web-application framework that includes everything needed to create +database-backed web applications according to the Model-View-Control pattern. + +This pattern splits the view (also called the presentation) into "dumb" +templates that are primarily responsible for inserting pre-built data in between +HTML tags. The model contains the "smart" domain objects (such as Account, +Product, Person, Post) that holds all the business logic and knows how to +persist themselves to a database. The controller handles the incoming requests +(such as Save New Account, Update Product, Show Post) by manipulating the model +and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, create a new Rails application: + rails new myapp (where myapp is the application name) + +2. Change directory to myapp and start the web server: + cd myapp; rails server (run with --help for options) + +3. Go to http://localhost:3000/ and you'll see: + "Welcome aboard: You're riding Ruby on Rails!" + +4. Follow the guidelines to start developing your application. You can find +the following resources handy: + +* The Getting Started Guide: http://guides.rubyonrails.org/getting_started.html +* Ruby on Rails Tutorial Book: http://www.railstutorial.org/ + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands +running on the server.log and development.log. Rails will automatically display +debugging and runtime information to these files. Debugging info will also be +shown in the browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code +using the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1! + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/. There are +several books available online as well: + +* Programming Ruby: http://www.ruby-doc.org/docs/ProgrammingRuby/ (Pickaxe) +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two books will bring you up to speed on the Ruby language and also on +programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your +Mongrel or WEBrick server with --debugger. This means that you can break out of +execution at any point in the code, investigate and change the model, and then, +resume execution! You need to install ruby-debug to run the server in debugging +mode. With gems, use sudo gem install ruby-debug. Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.all + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, "body"=>nil, "id"=>"1"}>, + #"Rails", "body"=>"Only ten..", "id"=>"2"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better, you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you can enter "cont". + + +== Console + +The console is a Ruby shell, which allows you to interact with your +application's domain model. Here you'll have all parts of the application +configured, just like it is when the application is running. You can inspect +domain models, change values, and save to the database. Starting the script +without arguments will launch it in the development environment. + +To start the console, run rails console from the application +directory. + +Options: + +* Passing the -s, --sandbox argument will rollback any modifications + made to the database. +* Passing an environment name as an argument will load the corresponding + environment. Example: rails console production. + +To reload your controllers and models after launching the console run +reload! + +More information about irb can be found at: +link:http://www.rubycentral.org/pickaxe/irb.html + + +== dbconsole + +You can go to the command line of your database directly through rails +dbconsole. You would be connected to the database with the credentials +defined in database.yml. Starting the script without arguments will connect you +to the development database. Passing an argument will connect you to a different +database, like rails dbconsole production. Currently works for MySQL, +PostgreSQL and SQLite 3. + +== Description of Contents + +The default directory structure of a generated Ruby on Rails application: + + |-- app + | |-- assets + | | |-- images + | | |-- javascripts + | | `-- stylesheets + | |-- controllers + | |-- helpers + | |-- mailers + | |-- models + | `-- views + | `-- layouts + |-- config + | |-- environments + | |-- initializers + | `-- locales + |-- db + |-- doc + |-- lib + | |-- assets + | `-- tasks + |-- log + |-- public + |-- script + |-- test + | |-- fixtures + | |-- functional + | |-- integration + | |-- performance + | `-- unit + |-- tmp + | `-- cache + | `-- assets + `-- vendor + |-- assets + | |-- javascripts + | `-- stylesheets + `-- plugins + +app + Holds all the code that's specific to this particular application. + +app/assets + Contains subdirectories for images, stylesheets, and JavaScript files. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from + ApplicationController which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. Models descend from + ActiveRecord::Base by default. + +app/views + Holds the template files for the view that should be named like + weblogs/index.html.erb for the WeblogsController#index action. All views use + eRuby syntax by default. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the + common header/footer method of wrapping views. In your views, define a layout + using the layout :default and create a file named default.html.erb. + Inside default.html.erb, call <% yield %> to render the view using this + layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are + generated for you automatically when using generators for controllers. + Helpers can be used to wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, + and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all the + sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when + generated using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that + doesn't belong under controllers, models, or helpers. This directory is in + the load path. + +public + The directory available for the web server. Also contains the dispatchers and the + default HTML files. This should be set as the DOCUMENT_ROOT of your web + server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the rails generate + command, template test files will be generated for you and placed in this + directory. + +vendor + External libraries that the application depends on. Also includes the plugins + subdirectory. If the app has frozen rails, those gems also go here, under + vendor/rails/. This directory is in the load path. diff --git a/test/dummy/Rakefile b/test/dummy/Rakefile new file mode 100644 index 0000000..3645852 --- /dev/null +++ b/test/dummy/Rakefile @@ -0,0 +1,7 @@ +#!/usr/bin/env rake +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require File.expand_path('../config/application', __FILE__) + +Dummy::Application.load_tasks diff --git a/test/dummy/app/assets/javascripts/application.js b/test/dummy/app/assets/javascripts/application.js new file mode 100644 index 0000000..9097d83 --- /dev/null +++ b/test/dummy/app/assets/javascripts/application.js @@ -0,0 +1,15 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// the compiled file. +// +// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD +// GO AFTER THE REQUIRES BELOW. +// +//= require jquery +//= require jquery_ujs +//= require_tree . diff --git a/test/dummy/app/assets/stylesheets/application.css b/test/dummy/app/assets/stylesheets/application.css new file mode 100644 index 0000000..3192ec8 --- /dev/null +++ b/test/dummy/app/assets/stylesheets/application.css @@ -0,0 +1,13 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the top of the + * compiled file, but it's generally better to create a new file per style scope. + * + *= require_self + *= require_tree . + */ diff --git a/test/dummy/app/controllers/application_controller.rb b/test/dummy/app/controllers/application_controller.rb new file mode 100644 index 0000000..e8065d9 --- /dev/null +++ b/test/dummy/app/controllers/application_controller.rb @@ -0,0 +1,3 @@ +class ApplicationController < ActionController::Base + protect_from_forgery +end diff --git a/test/dummy/app/helpers/application_helper.rb b/test/dummy/app/helpers/application_helper.rb new file mode 100644 index 0000000..de6be79 --- /dev/null +++ b/test/dummy/app/helpers/application_helper.rb @@ -0,0 +1,2 @@ +module ApplicationHelper +end diff --git a/test/dummy/app/mailers/.gitkeep b/test/dummy/app/mailers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/app/models/.gitkeep b/test/dummy/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/app/views/layouts/application.html.erb b/test/dummy/app/views/layouts/application.html.erb new file mode 100644 index 0000000..4cab268 --- /dev/null +++ b/test/dummy/app/views/layouts/application.html.erb @@ -0,0 +1,14 @@ + + + + Dummy + <%= stylesheet_link_tag "application", :media => "all" %> + <%= javascript_include_tag "application" %> + <%= csrf_meta_tags %> + + + +<%= yield %> + + + diff --git a/test/dummy/config.ru b/test/dummy/config.ru new file mode 100644 index 0000000..1989ed8 --- /dev/null +++ b/test/dummy/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Dummy::Application diff --git a/test/dummy/config/application.rb b/test/dummy/config/application.rb new file mode 100644 index 0000000..88e9d51 --- /dev/null +++ b/test/dummy/config/application.rb @@ -0,0 +1,65 @@ +require File.expand_path('../boot', __FILE__) + +# Pick the frameworks you want: +# require "active_record/railtie" +require "action_controller/railtie" +require "action_mailer/railtie" +require "active_resource/railtie" +require "sprockets/railtie" +require "rails/test_unit/railtie" + +Bundler.require(*Rails.groups) +require "calendar" + +module Dummy + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Custom directories with classes and modules you want to be autoloadable. + # config.autoload_paths += %W(#{config.root}/extras) + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named. + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Activate observers that should always be running. + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + + # Enable escaping HTML in JSON. + config.active_support.escape_html_entities_in_json = true + + # Use SQL instead of Active Record's schema dumper when creating the database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Enforce whitelist mode for mass assignment. + # This will create an empty whitelist of attributes available for mass-assignment for all models + # in your app. As such, your models will need to explicitly whitelist or blacklist accessible + # parameters by using an attr_accessible or attr_protected declaration. + # config.active_record.whitelist_attributes = true + + # Enable the asset pipeline + config.assets.enabled = true + + # Version of your assets, change this if you want to expire all your assets + config.assets.version = '1.0' + end +end + diff --git a/test/dummy/config/boot.rb b/test/dummy/config/boot.rb new file mode 100644 index 0000000..eba0681 --- /dev/null +++ b/test/dummy/config/boot.rb @@ -0,0 +1,10 @@ +require 'rubygems' +gemfile = File.expand_path('../../../../Gemfile', __FILE__) + +if File.exist?(gemfile) + ENV['BUNDLE_GEMFILE'] = gemfile + require 'bundler' + Bundler.setup +end + +$:.unshift File.expand_path('../../../../lib', __FILE__) \ No newline at end of file diff --git a/test/dummy/config/environment.rb b/test/dummy/config/environment.rb new file mode 100644 index 0000000..3da5eb9 --- /dev/null +++ b/test/dummy/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +Dummy::Application.initialize! diff --git a/test/dummy/config/environments/development.rb b/test/dummy/config/environments/development.rb new file mode 100644 index 0000000..493c477 --- /dev/null +++ b/test/dummy/config/environments/development.rb @@ -0,0 +1,31 @@ +Dummy::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # In the development environment your application's code is reloaded on + # every request. This slows down response time but is perfect for development + # since you don't have to restart the web server when you make code changes. + config.cache_classes = false + + # Log error messages when you accidentally call methods on nil. + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin + + + # Do not compress assets + config.assets.compress = false + + # Expands the lines which load the assets + config.assets.debug = true +end diff --git a/test/dummy/config/environments/production.rb b/test/dummy/config/environments/production.rb new file mode 100644 index 0000000..550acc5 --- /dev/null +++ b/test/dummy/config/environments/production.rb @@ -0,0 +1,64 @@ +Dummy::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # Code is not reloaded between requests + config.cache_classes = true + + # Full error reports are disabled and caching is turned on + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Disable Rails's static asset server (Apache or nginx will already do this) + config.serve_static_assets = false + + # Compress JavaScripts and CSS + config.assets.compress = true + + # Don't fallback to assets pipeline if a precompiled asset is missed + config.assets.compile = false + + # Generate digests for assets URLs + config.assets.digest = true + + # Defaults to nil and saved in location specified by config.assets.prefix + # config.assets.manifest = YOUR_PATH + + # Specifies the header that your server uses for sending files + # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + # config.force_ssl = true + + # See everything in the log (default is :info) + # config.log_level = :debug + + # Prepend all log lines with the following tags + # config.log_tags = [ :subdomain, :uuid ] + + # Use a different logger for distributed setups + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) + # config.assets.precompile += %w( search.js ) + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found) + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify + +end diff --git a/test/dummy/config/environments/test.rb b/test/dummy/config/environments/test.rb new file mode 100644 index 0000000..54657bd --- /dev/null +++ b/test/dummy/config/environments/test.rb @@ -0,0 +1,35 @@ +Dummy::Application.configure do + # Settings specified here will take precedence over those in config/application.rb + + # The test environment is used exclusively to run your application's + # test suite. You never need to work with it otherwise. Remember that + # your test database is "scratch space" for the test suite and is wiped + # and recreated between test runs. Don't rely on the data there! + config.cache_classes = true + + # Configure static asset server for tests with Cache-Control for performance + config.serve_static_assets = true + config.static_cache_control = "public, max-age=3600" + + # Log error messages when you accidentally call methods on nil + config.whiny_nils = true + + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false + + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = false + + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false + + # Tell Action Mailer not to deliver emails to the real world. + # The :test delivery method accumulates sent emails in the + # ActionMailer::Base.deliveries array. + config.action_mailer.delivery_method = :test + + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/test/dummy/config/initializers/backtrace_silencers.rb b/test/dummy/config/initializers/backtrace_silencers.rb new file mode 100644 index 0000000..59385cd --- /dev/null +++ b/test/dummy/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/test/dummy/config/initializers/inflections.rb b/test/dummy/config/initializers/inflections.rb new file mode 100644 index 0000000..5d8d9be --- /dev/null +++ b/test/dummy/config/initializers/inflections.rb @@ -0,0 +1,15 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end +# +# These inflection rules are supported but not enabled by default: +# ActiveSupport::Inflector.inflections do |inflect| +# inflect.acronym 'RESTful' +# end diff --git a/test/dummy/config/initializers/mime_types.rb b/test/dummy/config/initializers/mime_types.rb new file mode 100644 index 0000000..72aca7e --- /dev/null +++ b/test/dummy/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/test/dummy/config/initializers/secret_token.rb b/test/dummy/config/initializers/secret_token.rb new file mode 100644 index 0000000..47c56b0 --- /dev/null +++ b/test/dummy/config/initializers/secret_token.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +Dummy::Application.config.secret_token = '235c60b779abd1ac3a6403222fb5542e37a86c6271f934d35a5a3eaad3f1ca3e7e0248a42a221699f26408da30ff5e2b455e68f06a0586a0ac8bba7b9e21b265' diff --git a/test/dummy/config/initializers/session_store.rb b/test/dummy/config/initializers/session_store.rb new file mode 100644 index 0000000..952473f --- /dev/null +++ b/test/dummy/config/initializers/session_store.rb @@ -0,0 +1,8 @@ +# Be sure to restart your server when you modify this file. + +Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rails generate session_migration") +# Dummy::Application.config.session_store :active_record_store diff --git a/test/dummy/config/initializers/wrap_parameters.rb b/test/dummy/config/initializers/wrap_parameters.rb new file mode 100644 index 0000000..369b465 --- /dev/null +++ b/test/dummy/config/initializers/wrap_parameters.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. +# +# This file contains settings for ActionController::ParamsWrapper which +# is enabled by default. + +# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. +ActiveSupport.on_load(:action_controller) do + wrap_parameters format: [:json] +end + diff --git a/test/dummy/config/locales/en.yml b/test/dummy/config/locales/en.yml new file mode 100644 index 0000000..179c14c --- /dev/null +++ b/test/dummy/config/locales/en.yml @@ -0,0 +1,5 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: "Hello world" diff --git a/test/dummy/config/routes.rb b/test/dummy/config/routes.rb new file mode 100644 index 0000000..eb8579b --- /dev/null +++ b/test/dummy/config/routes.rb @@ -0,0 +1,58 @@ +Dummy::Application.routes.draw do + # The priority is based upon order of creation: + # first created -> highest priority. + + # Sample of regular route: + # match 'products/:id' => 'catalog#view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase + # This route can be invoked with purchase_url(:id => product.id) + + # Sample resource route (maps HTTP verbs to controller actions automatically): + # resources :products + + # Sample resource route with options: + # resources :products do + # member do + # get 'short' + # post 'toggle' + # end + # + # collection do + # get 'sold' + # end + # end + + # Sample resource route with sub-resources: + # resources :products do + # resources :comments, :sales + # resource :seller + # end + + # Sample resource route with more complex sub-resources + # resources :products do + # resources :comments + # resources :sales do + # get 'recent', :on => :collection + # end + # end + + # Sample resource route within a namespace: + # namespace :admin do + # # Directs /admin/products/* to Admin::ProductsController + # # (app/controllers/admin/products_controller.rb) + # resources :products + # end + + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + # root :to => 'welcome#index' + + # See how all your routes lay out with "rake routes" + + # This is a legacy wild controller route that's not recommended for RESTful applications. + # Note: This route will make all actions in every controller accessible via GET requests. + # match ':controller(/:action(/:id))(.:format)' +end diff --git a/test/dummy/lib/assets/.gitkeep b/test/dummy/lib/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/log/.gitkeep b/test/dummy/log/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/public/404.html b/test/dummy/public/404.html new file mode 100644 index 0000000..9a48320 --- /dev/null +++ b/test/dummy/public/404.html @@ -0,0 +1,26 @@ + + + + The page you were looking for doesn't exist (404) + + + + + +
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+ + diff --git a/test/dummy/public/422.html b/test/dummy/public/422.html new file mode 100644 index 0000000..83660ab --- /dev/null +++ b/test/dummy/public/422.html @@ -0,0 +1,26 @@ + + + + The change you wanted was rejected (422) + + + + + +
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+ + diff --git a/test/dummy/public/500.html b/test/dummy/public/500.html new file mode 100644 index 0000000..f3648a0 --- /dev/null +++ b/test/dummy/public/500.html @@ -0,0 +1,25 @@ + + + + We're sorry, but something went wrong (500) + + + + + +
+

We're sorry, but something went wrong.

+
+ + diff --git a/test/dummy/public/favicon.ico b/test/dummy/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/test/dummy/script/rails b/test/dummy/script/rails new file mode 100755 index 0000000..f8da2cf --- /dev/null +++ b/test/dummy/script/rails @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. + +APP_PATH = File.expand_path('../../config/application', __FILE__) +require File.expand_path('../../config/boot', __FILE__) +require 'rails/commands' diff --git a/test/integration/navigation_test.rb b/test/integration/navigation_test.rb new file mode 100644 index 0000000..eec8c0e --- /dev/null +++ b/test/integration/navigation_test.rb @@ -0,0 +1,9 @@ +require 'test_helper' + +class NavigationTest < ActionDispatch::IntegrationTest + + # test "the truth" do + # assert true + # end +end + diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..1e26a31 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,15 @@ +# Configure Rails Environment +ENV["RAILS_ENV"] = "test" + +require File.expand_path("../dummy/config/environment.rb", __FILE__) +require "rails/test_help" + +Rails.backtrace_cleaner.remove_silencers! + +# Load support files +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } + +# Load fixtures from the engine +if ActiveSupport::TestCase.method_defined?(:fixture_path=) + ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) +end