event_calendar_engine 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. data/Gemfile +23 -0
  2. data/Gemfile.lock +161 -0
  3. data/README +1 -0
  4. data/Rakefile +39 -0
  5. data/VERSION +1 -0
  6. data/app/controllers/application_controller.rb +4 -0
  7. data/app/controllers/attendees_controller.rb +53 -0
  8. data/app/controllers/event_calendar/application_controller.rb +5 -0
  9. data/app/controllers/event_revisions_controller.rb +26 -0
  10. data/app/controllers/events_controller.rb +129 -0
  11. data/app/helpers/event_calendar/application_helper.rb +118 -0
  12. data/app/helpers/events_helper.rb +2 -0
  13. data/app/models/attendee.rb +6 -0
  14. data/app/models/deletable_instance_methods.rb +7 -0
  15. data/app/models/event.rb +124 -0
  16. data/app/models/event_revision.rb +9 -0
  17. data/app/models/participant.rb +23 -0
  18. data/app/models/participator.rb +20 -0
  19. data/app/views/attendees/index.html.erb +14 -0
  20. data/app/views/attendees/new.html.erb +13 -0
  21. data/app/views/event-calendar-shared/_flash.html.erb +7 -0
  22. data/app/views/event-calendar-shared/_main_menu.html.erb +3 -0
  23. data/app/views/event-calendar-shared/_navigation.html.erb +15 -0
  24. data/app/views/event_revisions/index.html.erb +31 -0
  25. data/app/views/event_revisions/show.html.erb +5 -0
  26. data/app/views/events/_browse_event_revisions.html.erb +19 -0
  27. data/app/views/events/_event.html.erb +10 -0
  28. data/app/views/events/_event_details.html.erb +26 -0
  29. data/app/views/events/_form.html.erb +36 -0
  30. data/app/views/events/attendees.html.erb +29 -0
  31. data/app/views/events/edit.html.erb +3 -0
  32. data/app/views/events/index.html.erb +41 -0
  33. data/app/views/events/new.html.erb +3 -0
  34. data/app/views/events/search.html.erb +11 -0
  35. data/app/views/events/show.html.erb +30 -0
  36. data/app/views/layouts/application.html.erb +49 -0
  37. data/config/application.rb +42 -0
  38. data/config/blueprint_settings.yml +10 -0
  39. data/config/boot.rb +13 -0
  40. data/config/cucumber.yml +8 -0
  41. data/config/database.example.yml +22 -0
  42. data/config/environment.rb +5 -0
  43. data/config/environments/development.rb +26 -0
  44. data/config/environments/production.rb +49 -0
  45. data/config/environments/test.rb +38 -0
  46. data/config/initializers/backtrace_silencers.rb +7 -0
  47. data/config/initializers/formtastic.rb +55 -0
  48. data/config/initializers/inflections.rb +10 -0
  49. data/config/initializers/mime_types.rb +5 -0
  50. data/config/initializers/secret_token.rb +9 -0
  51. data/config/initializers/session_store.rb +10 -0
  52. data/config/locales/en.yml +5 -0
  53. data/config/routes.rb +67 -0
  54. data/db/migrate/20101011142543_create_events.rb +19 -0
  55. data/db/migrate/20101011172027_create_attendees.rb +16 -0
  56. data/db/migrate/20101011200048_make_events_revisable.rb +25 -0
  57. data/db/schema.rb +45 -0
  58. data/db/seeds.rb +7 -0
  59. data/lib/event_calendar.rb +3 -0
  60. data/lib/event_calendar/engine.rb +25 -0
  61. data/lib/generators/event_calendar/install/USAGE +5 -0
  62. data/lib/generators/event_calendar/install/install_generator.rb +19 -0
  63. data/lib/generators/event_calendar/install/templates/event_calendar.rake +169 -0
  64. data/lib/tasks/blueprint.rake +25 -0
  65. data/lib/tasks/cucumber.rake +53 -0
  66. data/public/404.html +26 -0
  67. data/public/422.html +26 -0
  68. data/public/500.html +26 -0
  69. data/public/favicon.ico +0 -0
  70. data/public/images/rails.png +0 -0
  71. data/public/javascripts/event_calendar.js +62 -0
  72. data/public/javascripts/event_calendar_behaviors.js +131 -0
  73. data/public/javascripts/fullcalendar.js +3965 -0
  74. data/public/javascripts/jquery-ui-1.7.2.custom.min.js +298 -0
  75. data/public/javascripts/jquery.clonePosition.js +27 -0
  76. data/public/javascripts/jquery.js +154 -0
  77. data/public/javascripts/jquery.qtip-1.0.0-rc3.js +2149 -0
  78. data/public/javascripts/jquery.string.1.0-min.js +6 -0
  79. data/public/javascripts/jquery.tablesorter.min.js +2 -0
  80. data/public/javascripts/lowpro.jquery.js +224 -0
  81. data/public/javascripts/rails.js +132 -0
  82. data/public/robots.txt +5 -0
  83. data/public/stylesheets/blueprint/grid.css +280 -0
  84. data/public/stylesheets/blueprint/icons/cross.png +0 -0
  85. data/public/stylesheets/blueprint/icons/doc.png +0 -0
  86. data/public/stylesheets/blueprint/icons/email.png +0 -0
  87. data/public/stylesheets/blueprint/icons/external.png +0 -0
  88. data/public/stylesheets/blueprint/icons/feed.png +0 -0
  89. data/public/stylesheets/blueprint/icons/im.png +0 -0
  90. data/public/stylesheets/blueprint/icons/key.png +0 -0
  91. data/public/stylesheets/blueprint/icons/pdf.png +0 -0
  92. data/public/stylesheets/blueprint/icons/tick.png +0 -0
  93. data/public/stylesheets/blueprint/icons/visited.png +0 -0
  94. data/public/stylesheets/blueprint/icons/xls.png +0 -0
  95. data/public/stylesheets/blueprint/ie.css +36 -0
  96. data/public/stylesheets/blueprint/plugins/buttons/icons/cross.png +0 -0
  97. data/public/stylesheets/blueprint/plugins/buttons/icons/key.png +0 -0
  98. data/public/stylesheets/blueprint/plugins/buttons/icons/tick.png +0 -0
  99. data/public/stylesheets/blueprint/plugins/buttons/readme.txt +32 -0
  100. data/public/stylesheets/blueprint/plugins/buttons/screen.css +97 -0
  101. data/public/stylesheets/blueprint/plugins/fancy-type/readme.txt +14 -0
  102. data/public/stylesheets/blueprint/plugins/fancy-type/screen.css +71 -0
  103. data/public/stylesheets/blueprint/plugins/link-icons/icons/doc.png +0 -0
  104. data/public/stylesheets/blueprint/plugins/link-icons/icons/email.png +0 -0
  105. data/public/stylesheets/blueprint/plugins/link-icons/icons/external.png +0 -0
  106. data/public/stylesheets/blueprint/plugins/link-icons/icons/feed.png +0 -0
  107. data/public/stylesheets/blueprint/plugins/link-icons/icons/im.png +0 -0
  108. data/public/stylesheets/blueprint/plugins/link-icons/icons/pdf.png +0 -0
  109. data/public/stylesheets/blueprint/plugins/link-icons/icons/visited.png +0 -0
  110. data/public/stylesheets/blueprint/plugins/link-icons/icons/xls.png +0 -0
  111. data/public/stylesheets/blueprint/plugins/link-icons/readme.txt +18 -0
  112. data/public/stylesheets/blueprint/plugins/link-icons/screen.css +40 -0
  113. data/public/stylesheets/blueprint/plugins/rtl/readme.txt +10 -0
  114. data/public/stylesheets/blueprint/plugins/rtl/screen.css +110 -0
  115. data/public/stylesheets/blueprint/plugins/silksprite/sprite.css +1 -0
  116. data/public/stylesheets/blueprint/plugins/silksprite/sprites.png +0 -0
  117. data/public/stylesheets/blueprint/print.css +29 -0
  118. data/public/stylesheets/blueprint/readme.txt +12 -0
  119. data/public/stylesheets/blueprint/screen.css +429 -0
  120. data/public/stylesheets/error_messages.css +65 -0
  121. data/public/stylesheets/formtastic.css +131 -0
  122. data/public/stylesheets/formtastic_changes.css +14 -0
  123. data/public/stylesheets/fullcalendar.css +574 -0
  124. data/public/stylesheets/fullcalendar_changes.css +0 -0
  125. data/public/stylesheets/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  126. data/public/stylesheets/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  127. data/public/stylesheets/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  128. data/public/stylesheets/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  129. data/public/stylesheets/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  130. data/public/stylesheets/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  131. data/public/stylesheets/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  132. data/public/stylesheets/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  133. data/public/stylesheets/smoothness/images/ui-icons_222222_256x240.png +0 -0
  134. data/public/stylesheets/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  135. data/public/stylesheets/smoothness/images/ui-icons_454545_256x240.png +0 -0
  136. data/public/stylesheets/smoothness/images/ui-icons_888888_256x240.png +0 -0
  137. data/public/stylesheets/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  138. data/public/stylesheets/smoothness/jquery-ui-1.7.2.custom.css +406 -0
  139. data/public/stylesheets/tablesorter/blue/asc.gif +0 -0
  140. data/public/stylesheets/tablesorter/blue/bg.gif +0 -0
  141. data/public/stylesheets/tablesorter/blue/desc.gif +0 -0
  142. data/public/stylesheets/tablesorter/blue/style.css +39 -0
  143. data/public/stylesheets/text_and_colors.css +49 -0
  144. data/spec/controllers/attendees_controller_spec.rb +27 -0
  145. data/spec/controllers/event_revisions_controller_spec.rb +86 -0
  146. data/spec/controllers/events_controller_spec.rb +168 -0
  147. data/spec/fixtures/event_calendar_events.yml +8 -0
  148. data/spec/models/deletable_instance_methods_spec.rb +61 -0
  149. data/spec/models/event_revision_spec.rb +36 -0
  150. data/spec/models/event_spec.rb +139 -0
  151. data/spec/spec_helper.rb +27 -0
  152. data/spec/spec_helpers/mocks.rb +6 -0
  153. data/vendor/plugins/searchable_by/MIT-LICENSE +20 -0
  154. data/vendor/plugins/searchable_by/README +55 -0
  155. data/vendor/plugins/searchable_by/Rakefile +23 -0
  156. data/vendor/plugins/searchable_by/init.rb +2 -0
  157. data/vendor/plugins/searchable_by/install.rb +1 -0
  158. data/vendor/plugins/searchable_by/lib/searchable_by.rb +137 -0
  159. data/vendor/plugins/searchable_by/tasks/searchable_by_tasks.rake +4 -0
  160. data/vendor/plugins/searchable_by/test/boot.rb +21 -0
  161. data/vendor/plugins/searchable_by/test/database.yml +22 -0
  162. data/vendor/plugins/searchable_by/test/fixtures/companies.yml +10 -0
  163. data/vendor/plugins/searchable_by/test/fixtures/company.rb +5 -0
  164. data/vendor/plugins/searchable_by/test/fixtures/employee.rb +5 -0
  165. data/vendor/plugins/searchable_by/test/fixtures/employees.yml +28 -0
  166. data/vendor/plugins/searchable_by/test/fixtures/schema.rb +18 -0
  167. data/vendor/plugins/searchable_by/test/helper.rb +12 -0
  168. data/vendor/plugins/searchable_by/test/lib/activerecord_test_case.rb +43 -0
  169. data/vendor/plugins/searchable_by/test/lib/activerecord_test_connector.rb +75 -0
  170. data/vendor/plugins/searchable_by/test/lib/load_fixtures.rb +9 -0
  171. data/vendor/plugins/searchable_by/test/searchable_by_test.rb +73 -0
  172. data/vendor/plugins/searchable_by/uninstall.rb +1 -0
  173. metadata +606 -0
@@ -0,0 +1,25 @@
1
+ namespace :blueprint do
2
+ desc "Install/Update blueprint stylesheets (+plugins)"
3
+ task :install do
4
+ blueprint_css = File.expand_path("~/.blueprint_css")
5
+
6
+ if File.exists?(blueprint_css)
7
+ blueprint_path = File.new(blueprint_css, 'r').read.strip
8
+ else
9
+ puts("What is the path to your blueprint checkout?")
10
+ blueprint_path = $stdin.gets.strip
11
+ end
12
+
13
+ compress_script = File.join(blueprint_path, 'lib', 'compress.rb')
14
+
15
+ while !File.exists?(compress_script)
16
+ puts("Could not find #{compress_script}. Please enter new path.")
17
+ blueprint_path = $stdin.gets.strip
18
+ compress_script = File.join(blueprint_path, 'lib', 'compress.rb')
19
+ end
20
+
21
+ File.open(blueprint_css, 'w'){|f| f.write(blueprint_path)}
22
+
23
+ system "ruby #{compress_script} -f #{Rails.root.to_s}/config/blueprint_settings.yml -p default"
24
+ end
25
+ end
@@ -0,0 +1,53 @@
1
+ # IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
2
+ # It is recommended to regenerate this file in the future when you upgrade to a
3
+ # newer version of cucumber-rails. Consider adding your own code to a new file
4
+ # instead of editing this one. Cucumber will automatically load all features/**/*.rb
5
+ # files.
6
+
7
+
8
+ unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks
9
+
10
+ vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first
11
+ $LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil?
12
+
13
+ begin
14
+ require 'cucumber/rake/task'
15
+
16
+ namespace :cucumber do
17
+ Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
18
+ t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
19
+ t.fork = true # You may get faster startup if you set this to false
20
+ t.profile = 'default'
21
+ end
22
+
23
+ Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
24
+ t.binary = vendored_cucumber_bin
25
+ t.fork = true # You may get faster startup if you set this to false
26
+ t.profile = 'wip'
27
+ end
28
+
29
+ Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
30
+ t.binary = vendored_cucumber_bin
31
+ t.fork = true # You may get faster startup if you set this to false
32
+ t.profile = 'rerun'
33
+ end
34
+
35
+ desc 'Run all features'
36
+ task :all => [:ok, :wip]
37
+ end
38
+ desc 'Alias for cucumber:ok'
39
+ task :cucumber => 'cucumber:ok'
40
+
41
+ task :default => :cucumber
42
+
43
+ task :features => :cucumber do
44
+ STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
45
+ end
46
+ rescue LoadError
47
+ desc 'cucumber rake task not available (cucumber not installed)'
48
+ task :cucumber do
49
+ abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin'
50
+ end
51
+ end
52
+
53
+ end
data/public/404.html ADDED
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
data/public/422.html ADDED
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
data/public/500.html ADDED
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ <p>We've been notified about this issue and we'll take a look at it shortly.</p>
24
+ </div>
25
+ </body>
26
+ </html>
File without changes
Binary file
@@ -0,0 +1,62 @@
1
+ // use to give the preview of details for an event below a calendar
2
+ var updateEventDescription = function(event, jsEvent) {
3
+ $("#event_quick_description")[0].innerHTML = "";
4
+ $("#event_quick_description").append(
5
+ $("<h3/>").append(
6
+ $('<a/>', { text : event.title, href : event.url })
7
+ )
8
+ );
9
+ $("#event_quick_description")[0].innerHTML += "Location: " + event.location + "<br/>";
10
+ $("#event_quick_description")[0].innerHTML += event.description;
11
+
12
+ $("#event_quick_description").show();
13
+ }
14
+
15
+
16
+ jQuery(function($) {
17
+ $('a.show_hide_link').attach(ShowHideLink);
18
+ });
19
+
20
+ /*
21
+ http://www.learningjquery.com/2007/08/clearing-form-data
22
+ */
23
+ $.fn.clearForm = function() {
24
+ return this.each(function() {
25
+ var type = this.type, tag = this.tagName.toLowerCase();
26
+ if (tag == 'form')
27
+ return $(':input',this).clearForm();
28
+ if (type == 'text' || type == 'password' || tag == 'textarea')
29
+ this.value = '';
30
+ else if (type == 'checkbox' || type == 'radio')
31
+ this.checked = false;
32
+ else if (tag == 'select')
33
+ this.selectedIndex = -1;
34
+ });
35
+ };
36
+
37
+ DynamicForm = $.klass({
38
+ initialize: function(options) {
39
+ this.formId = options.formId; // new_description
40
+ this.formContainer = options.formContainer; // blank_description_form
41
+ this.targetIdName = options.targetIdName; // file_attachment_id
42
+ this.targetContentName = options.targetContentName; // file_attachment[description]
43
+ this.targetContentType = options.targetContentType;
44
+ this.actionPrefix = options.actionPrefix; // /file_attachments
45
+ },
46
+ onclick: function(e) {
47
+ e.preventDefault();
48
+
49
+ var targetIdValue = this.element.attr(this.targetIdName);
50
+ var targetContentValue = this.element.attr(this.targetContentName);
51
+
52
+ $('#' + this.formId).attr("action", this.actionPrefix + "/" + targetIdValue);
53
+
54
+ $('#' + this.formId).clearForm();
55
+
56
+ $('#' + this.formContainer).insertBefore(this.element);
57
+
58
+ $(this.targetContentType + '[name='+ this.targetContentName +']').val(targetContentValue);
59
+
60
+ $('#' + this.formContainer).show();
61
+ }
62
+ });
@@ -0,0 +1,131 @@
1
+ /*
2
+ * all lowpro behaviors
3
+ */
4
+
5
+
6
+ CheckBoxToggle = $.klass({
7
+ initialize: function(toggle_selector) {
8
+ this.toggle_selector = toggle_selector;
9
+ },
10
+
11
+ onclick: function() {
12
+ var to_toggle = this.element[0].checked;
13
+ $(this.toggle_selector + ' input[type=checkbox]').each(function(index) {
14
+ $(this)[0].checked = to_toggle;
15
+ });
16
+ }
17
+ });
18
+
19
+ /*
20
+ * use like:
21
+ *
22
+ * $('.some_category_field').attach(SelectPopper, '#div_with_ul');
23
+ * $('#div_with_ul').attach(SetSelector, '.some_category_field');
24
+ *
25
+ */
26
+
27
+ SelectPopper = $.klass({
28
+ // provide array of select_options
29
+ // TODO: detect a hash and use it to do option text/value
30
+ initialize: function(select_options) {
31
+ if(select_options.length > 0) {
32
+ var _a_ul = $('<ul/>', { style : 'list-style-type: none; margin: 0' } );
33
+ $.each(select_options, function(index, value) {
34
+ $('<li/>', { text : value, style : "padding: 3px;" }).appendTo(_a_ul);
35
+ });
36
+
37
+ this.select_box = $('<div/>', { style : 'display:none; background: white; border: 1px solid #999;',
38
+ "class" : "select_popper_box" } );
39
+ _a_ul.appendTo(this.select_box);
40
+ this.select_box.appendTo('body');
41
+ this.select_box.attach(SetSelector, this.element);
42
+ }
43
+ },
44
+
45
+ onfocus: function(e) {
46
+ if(this.select_box) {
47
+ this.select_box.clonePosition(this.element, { cloneHeight: false, offsetLeft: 1, offsetTop: this.element.height() });
48
+ this.select_box.css('width', (this.element.offsetWidth - 2) + 'px');
49
+ this.select_box.show();
50
+ }
51
+ },
52
+
53
+ onblur: function(e) {
54
+ if(this.select_box) {
55
+ var _select_box = this.select_box;
56
+ setTimeout(function() {
57
+ _select_box.hide();
58
+ }, 250);
59
+ }
60
+ }
61
+ });
62
+
63
+ SetSelector = $.klass({
64
+ initialize: function(input_element) {
65
+ this.select_field = $(input_element);
66
+ },
67
+
68
+ onclick: $.delegate({
69
+ 'li': function(click_target) {
70
+ this.select_field.val(click_target.text());
71
+ this.element.hide();
72
+ }
73
+ })
74
+ });
75
+
76
+
77
+ TextareaExpander = $.klass({
78
+ initialize: function(pixelsToGrowBy, maxHeight) {
79
+ this.pixelsToGrowBy = (typeof pixelsToGrowBy == 'undefined') ? 20 : pixelsToGrowBy;
80
+ this.maxHeight = (typeof maxHeight == 'undefined') ? 999 : maxHeight;
81
+ // run it once to blow up any textareas on first page load
82
+ this.onkeypress();
83
+ },
84
+
85
+ onkeypress: function(e) {
86
+ var curr_h = this.element.height();
87
+ var scroll_h = this.element[0].scrollHeight;
88
+ while(curr_h < scroll_h && curr_h < this.maxHeight) {
89
+ this.element.css( { 'height': (curr_h + this.pixelsToGrowBy) + 'px' });
90
+ curr_h = this.element.height();
91
+ scroll_h = this.element[0].scrollHeight;
92
+ }
93
+ }
94
+ });
95
+
96
+
97
+ ShowHideLink = $.klass({
98
+ initialize: function(options) {
99
+ options = options || {};
100
+ this.showEffect = options.show_effect;
101
+ this.hideEffect = options.hide_effect;
102
+ this.showEffectParams = options.show_effect_params;
103
+ this.hideEffectParams = options.hide_effect_params;
104
+ this.showClassName = $.string(this.element[0].href).toQueryParams()['show'];
105
+ this.hideClassName = $.string(this.element[0].href).toQueryParams()['hide'];
106
+ this.toggleClassName = $.string(this.element[0].href).toQueryParams()['toggle'];
107
+ this.hideMe = options.hide_me;
108
+ this.doNotStop = options.do_not_stop || false;
109
+ this.myToggleClass = options.my_toggle_class;
110
+ },
111
+
112
+ onclick: function(e) {
113
+ if(this.hideClassName) {
114
+ //$('.'+this.hideClassName).invoke(this.hideEffect || 'hide', this.hideEffectParams);
115
+ $('.'+this.hideClassName).hide();
116
+ }
117
+ if(this.showClassName) {
118
+ //$('.'+this.showClassName).invoke(this.showEffect || 'show', this.showEffectParams);
119
+ $('.'+this.showClassName).show();
120
+ }
121
+ if(this.toggleClassName) {
122
+ $('.'+this.toggleClassName).toggle();
123
+ }
124
+ if(this.hideMe) {
125
+ this.element.hide();
126
+ }
127
+ this.element.blur();
128
+
129
+ return this.doNotStop;
130
+ }
131
+ });
@@ -0,0 +1,3965 @@
1
+ /**
2
+ * @preserve
3
+ * FullCalendar v1.4.6
4
+ * http://arshaw.com/fullcalendar/
5
+ *
6
+ * Use fullcalendar.css for basic styling.
7
+ * For event drag & drop, required jQuery UI draggable.
8
+ * For event resizing, requires jQuery UI resizable.
9
+ *
10
+ * Copyright (c) 2009 Adam Shaw
11
+ * Dual licensed under the MIT and GPL licenses:
12
+ * http://www.opensource.org/licenses/mit-license.php
13
+ * http://www.gnu.org/licenses/gpl.html
14
+ *
15
+ * Date: Mon May 31 10:18:29 2010 -0700
16
+ *
17
+ */
18
+
19
+ (function($) {
20
+
21
+
22
+ var fc = $.fullCalendar = {};
23
+ var views = fc.views = {};
24
+
25
+
26
+ /* Defaults
27
+ -----------------------------------------------------------------------------*/
28
+
29
+ var defaults = {
30
+
31
+ // display
32
+ defaultView: 'month',
33
+ aspectRatio: 1.35,
34
+ header: {
35
+ left: 'title',
36
+ center: '',
37
+ right: 'today prev,next'
38
+ },
39
+ weekends: true,
40
+
41
+ // editing
42
+ //editable: false,
43
+ //disableDragging: false,
44
+ //disableResizing: false,
45
+
46
+ allDayDefault: true,
47
+
48
+ // event ajax
49
+ lazyFetching: true,
50
+ startParam: 'start',
51
+ endParam: 'end',
52
+
53
+ // time formats
54
+ titleFormat: {
55
+ month: 'MMMM yyyy',
56
+ week: "MMM d[ yyyy]{ '&#8212;'[ MMM] d yyyy}",
57
+ day: 'dddd, MMM d, yyyy'
58
+ },
59
+ columnFormat: {
60
+ month: 'ddd',
61
+ week: 'ddd M/d',
62
+ day: 'dddd M/d'
63
+ },
64
+ timeFormat: { // for event elements
65
+ '': 'h(:mm)t' // default
66
+ },
67
+
68
+ // locale
69
+ isRTL: false,
70
+ firstDay: 0,
71
+ monthNames: ['January','February','March','April','May','June','July','August','September','October','November','December'],
72
+ monthNamesShort: ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'],
73
+ dayNames: ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],
74
+ dayNamesShort: ['Sun','Mon','Tue','Wed','Thu','Fri','Sat'],
75
+ buttonText: {
76
+ prev: '&nbsp;&#9668;&nbsp;',
77
+ next: '&nbsp;&#9658;&nbsp;',
78
+ prevYear: '&nbsp;&lt;&lt;&nbsp;',
79
+ nextYear: '&nbsp;&gt;&gt;&nbsp;',
80
+ today: 'today',
81
+ month: 'month',
82
+ week: 'week',
83
+ day: 'day'
84
+ },
85
+
86
+ // jquery-ui theming
87
+ theme: false,
88
+ buttonIcons: {
89
+ prev: 'circle-triangle-w',
90
+ next: 'circle-triangle-e'
91
+ },
92
+
93
+ //selectable: false,
94
+ unselectAuto: true
95
+
96
+ };
97
+
98
+ // right-to-left defaults
99
+ var rtlDefaults = {
100
+ header: {
101
+ left: 'next,prev today',
102
+ center: '',
103
+ right: 'title'
104
+ },
105
+ buttonText: {
106
+ prev: '&nbsp;&#9658;&nbsp;',
107
+ next: '&nbsp;&#9668;&nbsp;',
108
+ prevYear: '&nbsp;&gt;&gt;&nbsp;',
109
+ nextYear: '&nbsp;&lt;&lt;&nbsp;'
110
+ },
111
+ buttonIcons: {
112
+ prev: 'circle-triangle-e',
113
+ next: 'circle-triangle-w'
114
+ }
115
+ };
116
+
117
+ // function for adding/overriding defaults
118
+ var setDefaults = fc.setDefaults = function(d) {
119
+ $.extend(true, defaults, d);
120
+ };
121
+
122
+
123
+
124
+ /* .fullCalendar jQuery function
125
+ -----------------------------------------------------------------------------*/
126
+
127
+ $.fn.fullCalendar = function(options) {
128
+
129
+ // method calling
130
+ if (typeof options == 'string') {
131
+ var args = Array.prototype.slice.call(arguments, 1),
132
+ res;
133
+ this.each(function() {
134
+ var data = $.data(this, 'fullCalendar');
135
+ if (data) {
136
+ var meth = data[options];
137
+ if (meth) {
138
+ var r = meth.apply(this, args);
139
+ if (res === undefined) {
140
+ res = r;
141
+ }
142
+ }
143
+ }
144
+ });
145
+ if (res !== undefined) {
146
+ return res;
147
+ }
148
+ return this;
149
+ }
150
+
151
+ // pluck the 'events' and 'eventSources' options
152
+ var eventSources = options.eventSources || [];
153
+ delete options.eventSources;
154
+ if (options.events) {
155
+ eventSources.push(options.events);
156
+ delete options.events;
157
+ }
158
+
159
+ // first event source reserved for 'sticky' events
160
+ eventSources.unshift([]);
161
+
162
+ // initialize options
163
+ options = $.extend(true, {},
164
+ defaults,
165
+ (options.isRTL || options.isRTL===undefined && defaults.isRTL) ? rtlDefaults : {},
166
+ options
167
+ );
168
+ var tm = options.theme ? 'ui' : 'fc'; // for making theme classes
169
+
170
+
171
+ this.each(function() {
172
+
173
+
174
+ /* Instance Initialization
175
+ -----------------------------------------------------------------------------*/
176
+
177
+ // element
178
+ var _element = this,
179
+ element = $(_element).addClass('fc'),
180
+ elementOuterWidth,
181
+ content = $("<div class='fc-content " + tm + "-widget-content' style='position:relative'/>").prependTo(_element),
182
+ suggestedViewHeight,
183
+ resizeUID = 0,
184
+ ignoreWindowResize = 0,
185
+ date = new Date(),
186
+ viewName, // the current view name (TODO: look into getting rid of)
187
+ view, // the current view
188
+ viewInstances = {},
189
+ absoluteViewElement;
190
+
191
+
192
+
193
+ if (options.isRTL) {
194
+ element.addClass('fc-rtl');
195
+ }
196
+ if (options.theme) {
197
+ element.addClass('ui-widget');
198
+ }
199
+
200
+ if (options.year !== undefined && options.year != date.getFullYear()) {
201
+ date.setDate(1);
202
+ date.setMonth(0);
203
+ date.setFullYear(options.year);
204
+ }
205
+ if (options.month !== undefined && options.month != date.getMonth()) {
206
+ date.setDate(1);
207
+ date.setMonth(options.month);
208
+ }
209
+ if (options.date !== undefined) {
210
+ date.setDate(options.date);
211
+ }
212
+
213
+
214
+
215
+ /* View Rendering
216
+ -----------------------------------------------------------------------------*/
217
+
218
+ function changeView(v) {
219
+ if (v != viewName) {
220
+ ignoreWindowResize++; // because setMinHeight might change the height before render (and subsequently setSize) is reached
221
+
222
+ viewUnselect();
223
+
224
+ var oldView = view,
225
+ newViewElement;
226
+
227
+ if (oldView) {
228
+ if (oldView.eventsChanged) {
229
+ eventsDirty();
230
+ oldView.eventDirty = oldView.eventsChanged = false;
231
+ }
232
+ if (oldView.beforeHide) {
233
+ oldView.beforeHide(); // called before changing min-height. if called after, scroll state is reset (in Opera)
234
+ }
235
+ setMinHeight(content, content.height());
236
+ oldView.element.hide();
237
+ }else{
238
+ setMinHeight(content, 1); // needs to be 1 (not 0) for IE7, or else view dimensions miscalculated
239
+ }
240
+ content.css('overflow', 'hidden');
241
+
242
+ if (viewInstances[v]) {
243
+ (view = viewInstances[v]).element.show();
244
+ }else{
245
+ view = viewInstances[v] = fc.views[v](
246
+ newViewElement = absoluteViewElement =
247
+ $("<div class='fc-view fc-view-" + v + "' style='position:absolute'/>")
248
+ .appendTo(content),
249
+ options
250
+ );
251
+ }
252
+
253
+ if (header) {
254
+ // update 'active' view button
255
+ header.find('div.fc-button-' + viewName).removeClass(tm + '-state-active');
256
+ header.find('div.fc-button-' + v).addClass(tm + '-state-active');
257
+ }
258
+
259
+ view.name = viewName = v;
260
+ render(); // after height has been set, will make absoluteViewElement's position=relative, then set to null
261
+ content.css('overflow', '');
262
+ if (oldView) {
263
+ setMinHeight(content, 1);
264
+ }
265
+ if (!newViewElement && view.afterShow) {
266
+ view.afterShow(); // called after setting min-height/overflow, so in final scroll state (for Opera)
267
+ }
268
+
269
+ ignoreWindowResize--;
270
+ }
271
+ }
272
+
273
+
274
+ function render(inc) {
275
+ if (elementVisible()) {
276
+ ignoreWindowResize++; // because view.renderEvents might temporarily change the height before setSize is reached
277
+
278
+ viewUnselect();
279
+
280
+ if (suggestedViewHeight === undefined) {
281
+ calcSize();
282
+ }
283
+
284
+ if (!view.start || inc || date < view.start || date >= view.end) {
285
+ view.render(date, inc || 0); // responsible for clearing events
286
+ setSize(true);
287
+ if (!eventStart || !options.lazyFetching || view.visStart < eventStart || view.visEnd > eventEnd) {
288
+ fetchAndRenderEvents();
289
+ }else{
290
+ view.renderEvents(events); // don't refetch
291
+ }
292
+ }
293
+ else if (view.sizeDirty || view.eventsDirty || !options.lazyFetching) {
294
+ view.clearEvents();
295
+ if (view.sizeDirty) {
296
+ setSize();
297
+ }
298
+ if (options.lazyFetching) {
299
+ view.renderEvents(events); // don't refetch
300
+ }else{
301
+ fetchAndRenderEvents();
302
+ }
303
+ }
304
+ elementOuterWidth = element.outerWidth();
305
+ view.sizeDirty = false;
306
+ view.eventsDirty = false;
307
+
308
+ if (header) {
309
+ // update title text
310
+ header.find('h2.fc-header-title').html(view.title);
311
+ // enable/disable 'today' button
312
+ var today = new Date();
313
+ if (today >= view.start && today < view.end) {
314
+ header.find('div.fc-button-today').addClass(tm + '-state-disabled');
315
+ }else{
316
+ header.find('div.fc-button-today').removeClass(tm + '-state-disabled');
317
+ }
318
+ }
319
+
320
+ ignoreWindowResize--;
321
+ view.trigger('viewDisplay', _element);
322
+ }
323
+ }
324
+
325
+
326
+ function elementVisible() {
327
+ return _element.offsetWidth !== 0;
328
+ }
329
+
330
+ function bodyVisible() {
331
+ return $('body')[0].offsetWidth !== 0;
332
+ }
333
+
334
+ function viewUnselect() {
335
+ if (view) {
336
+ view.unselect();
337
+ }
338
+ }
339
+
340
+
341
+ // called when any event objects have been added/removed/changed, rerenders
342
+ function eventsChanged() {
343
+ eventsDirty();
344
+ if (elementVisible()) {
345
+ view.clearEvents();
346
+ view.renderEvents(events);
347
+ view.eventsDirty = false;
348
+ }
349
+ }
350
+
351
+ // marks other views' events as dirty
352
+ function eventsDirty() {
353
+ $.each(viewInstances, function() {
354
+ this.eventsDirty = true;
355
+ });
356
+ }
357
+
358
+ // called when we know the element size has changed
359
+ function sizeChanged() {
360
+ sizesDirty();
361
+ if (elementVisible()) {
362
+ calcSize();
363
+ setSize();
364
+ viewUnselect();
365
+ view.rerenderEvents();
366
+ view.sizeDirty = false;
367
+ }
368
+ }
369
+
370
+ // marks other views' sizes as dirty
371
+ function sizesDirty() {
372
+ $.each(viewInstances, function() {
373
+ this.sizeDirty = true;
374
+ });
375
+ }
376
+
377
+
378
+
379
+
380
+ /* Event Sources and Fetching
381
+ -----------------------------------------------------------------------------*/
382
+
383
+ var events = [],
384
+ eventStart, eventEnd;
385
+
386
+ // Fetch from ALL sources. Clear 'events' array and populate
387
+ function fetchEvents(callback) {
388
+ events = [];
389
+ eventStart = cloneDate(view.visStart);
390
+ eventEnd = cloneDate(view.visEnd);
391
+ var queued = eventSources.length,
392
+ sourceDone = function() {
393
+ if (!--queued) {
394
+ if (callback) {
395
+ callback(events);
396
+ }
397
+ }
398
+ }, i=0;
399
+ for (; i<eventSources.length; i++) {
400
+ fetchEventSource(eventSources[i], sourceDone);
401
+ }
402
+ }
403
+
404
+ // Fetch from a particular source. Append to the 'events' array
405
+ function fetchEventSource(src, callback) {
406
+ var prevViewName = view.name,
407
+ prevDate = cloneDate(date),
408
+ reportEvents = function(a) {
409
+ if (prevViewName == view.name && +prevDate == +date && // protects from fast switching
410
+ $.inArray(src, eventSources) != -1) { // makes sure source hasn't been removed
411
+ for (var i=0; i<a.length; i++) {
412
+ normalizeEvent(a[i], options);
413
+ a[i].source = src;
414
+ }
415
+ events = events.concat(a);
416
+ if (callback) {
417
+ callback(a);
418
+ }
419
+ }
420
+ },
421
+ reportEventsAndPop = function(a) {
422
+ reportEvents(a);
423
+ popLoading();
424
+ };
425
+ if (typeof src == 'string') {
426
+ var params = {};
427
+ params[options.startParam] = Math.round(eventStart.getTime() / 1000);
428
+ params[options.endParam] = Math.round(eventEnd.getTime() / 1000);
429
+ if (options.cacheParam) {
430
+ params[options.cacheParam] = (new Date()).getTime(); // TODO: deprecate cacheParam
431
+ }
432
+ pushLoading();
433
+ $.ajax({
434
+ url: src,
435
+ dataType: 'json',
436
+ data: params,
437
+ cache: options.cacheParam || false, // don't let jquery prevent caching if cacheParam is being used
438
+ success: reportEventsAndPop
439
+ });
440
+ }
441
+ else if ($.isFunction(src)) {
442
+ pushLoading();
443
+ src(cloneDate(eventStart), cloneDate(eventEnd), reportEventsAndPop);
444
+ }
445
+ else {
446
+ reportEvents(src); // src is an array
447
+ }
448
+ }
449
+
450
+
451
+ // for convenience
452
+ function fetchAndRenderEvents() {
453
+ fetchEvents(function(events) {
454
+ view.renderEvents(events); // maintain `this` in view
455
+ });
456
+ }
457
+
458
+
459
+
460
+ /* Loading State
461
+ -----------------------------------------------------------------------------*/
462
+
463
+ var loadingLevel = 0;
464
+
465
+ function pushLoading() {
466
+ if (!loadingLevel++) {
467
+ view.trigger('loading', _element, true);
468
+ }
469
+ }
470
+
471
+ function popLoading() {
472
+ if (!--loadingLevel) {
473
+ view.trigger('loading', _element, false);
474
+ }
475
+ }
476
+
477
+
478
+
479
+ /* Public Methods
480
+ -----------------------------------------------------------------------------*/
481
+
482
+ var publicMethods = {
483
+
484
+ render: function() {
485
+ calcSize();
486
+ sizesDirty();
487
+ eventsDirty();
488
+ render();
489
+ },
490
+
491
+ changeView: changeView,
492
+
493
+ getView: function() {
494
+ return view;
495
+ },
496
+
497
+ getDate: function() {
498
+ return date;
499
+ },
500
+
501
+ option: function(name, value) {
502
+ if (value === undefined) {
503
+ return options[name];
504
+ }
505
+ if (name == 'height' || name == 'contentHeight' || name == 'aspectRatio') {
506
+ options[name] = value;
507
+ sizeChanged();
508
+ }
509
+ },
510
+
511
+ destroy: function() {
512
+ $(window).unbind('resize', windowResize);
513
+ if (header) {
514
+ header.remove();
515
+ }
516
+ content.remove();
517
+ $.removeData(_element, 'fullCalendar');
518
+ },
519
+
520
+ //
521
+ // Navigation
522
+ //
523
+
524
+ prev: function() {
525
+ render(-1);
526
+ },
527
+
528
+ next: function() {
529
+ render(1);
530
+ },
531
+
532
+ prevYear: function() {
533
+ addYears(date, -1);
534
+ render();
535
+ },
536
+
537
+ nextYear: function() {
538
+ addYears(date, 1);
539
+ render();
540
+ },
541
+
542
+ today: function() {
543
+ date = new Date();
544
+ render();
545
+ },
546
+
547
+ gotoDate: function(year, month, dateNum) {
548
+ if (typeof year == 'object') {
549
+ date = cloneDate(year); // provided 1 argument, a Date
550
+ }else{
551
+ if (year !== undefined) {
552
+ date.setFullYear(year);
553
+ }
554
+ if (month !== undefined) {
555
+ date.setMonth(month);
556
+ }
557
+ if (dateNum !== undefined) {
558
+ date.setDate(dateNum);
559
+ }
560
+ }
561
+ render();
562
+ },
563
+
564
+ incrementDate: function(years, months, days) {
565
+ if (years !== undefined) {
566
+ addYears(date, years);
567
+ }
568
+ if (months !== undefined) {
569
+ addMonths(date, months);
570
+ }
571
+ if (days !== undefined) {
572
+ addDays(date, days);
573
+ }
574
+ render();
575
+ },
576
+
577
+ //
578
+ // Event Manipulation
579
+ //
580
+
581
+ updateEvent: function(event) { // update an existing event
582
+ var i, len = events.length, e,
583
+ startDelta = event.start - event._start,
584
+ endDelta = event.end ?
585
+ (event.end - (event._end || view.defaultEventEnd(event))) // event._end would be null if event.end
586
+ : 0; // was null and event was just resized
587
+ for (i=0; i<len; i++) {
588
+ e = events[i];
589
+ if (e._id == event._id && e != event) {
590
+ e.start = new Date(+e.start + startDelta);
591
+ if (event.end) {
592
+ if (e.end) {
593
+ e.end = new Date(+e.end + endDelta);
594
+ }else{
595
+ e.end = new Date(+view.defaultEventEnd(e) + endDelta);
596
+ }
597
+ }else{
598
+ e.end = null;
599
+ }
600
+ e.title = event.title;
601
+ e.url = event.url;
602
+ e.allDay = event.allDay;
603
+ e.className = event.className;
604
+ e.editable = event.editable;
605
+ normalizeEvent(e, options);
606
+ }
607
+ }
608
+ normalizeEvent(event, options);
609
+ eventsChanged();
610
+ },
611
+
612
+ renderEvent: function(event, stick) { // render a new event
613
+ normalizeEvent(event, options);
614
+ if (!event.source) {
615
+ if (stick) {
616
+ (event.source = eventSources[0]).push(event);
617
+ }
618
+ events.push(event);
619
+ }
620
+ eventsChanged();
621
+ },
622
+
623
+ removeEvents: function(filter) {
624
+ if (!filter) { // remove all
625
+ events = [];
626
+ // clear all array sources
627
+ for (var i=0; i<eventSources.length; i++) {
628
+ if (typeof eventSources[i] == 'object') {
629
+ eventSources[i] = [];
630
+ }
631
+ }
632
+ }else{
633
+ if (!$.isFunction(filter)) { // an event ID
634
+ var id = filter + '';
635
+ filter = function(e) {
636
+ return e._id == id;
637
+ };
638
+ }
639
+ events = $.grep(events, filter, true);
640
+ // remove events from array sources
641
+ for (var i=0; i<eventSources.length; i++) {
642
+ if (typeof eventSources[i] == 'object') {
643
+ eventSources[i] = $.grep(eventSources[i], filter, true);
644
+ }
645
+ }
646
+ }
647
+ eventsChanged();
648
+ },
649
+
650
+ clientEvents: function(filter) {
651
+ if ($.isFunction(filter)) {
652
+ return $.grep(events, filter);
653
+ }
654
+ else if (filter) { // an event ID
655
+ filter += '';
656
+ return $.grep(events, function(e) {
657
+ return e._id == filter;
658
+ });
659
+ }
660
+ return events; // else, return all
661
+ },
662
+
663
+ rerenderEvents: eventsChanged, // TODO: think of renaming eventsChanged
664
+
665
+ //
666
+ // Event Source
667
+ //
668
+
669
+ addEventSource: function(source) {
670
+ eventSources.push(source);
671
+ fetchEventSource(source, eventsChanged);
672
+ },
673
+
674
+ removeEventSource: function(source) {
675
+ eventSources = $.grep(eventSources, function(src) {
676
+ return src != source;
677
+ });
678
+ // remove all client events from that source
679
+ events = $.grep(events, function(e) {
680
+ return e.source != source;
681
+ });
682
+ eventsChanged();
683
+ },
684
+
685
+ refetchEvents: function() {
686
+ fetchEvents(eventsChanged);
687
+ },
688
+
689
+ //
690
+ // selection
691
+ //
692
+
693
+ select: function(start, end, allDay) {
694
+ view.select(start, end, allDay===undefined ? true : allDay);
695
+ },
696
+
697
+ unselect: function() {
698
+ view.unselect();
699
+ }
700
+
701
+ };
702
+
703
+ $.data(this, 'fullCalendar', publicMethods); // TODO: look into memory leak implications
704
+
705
+
706
+
707
+ /* Header
708
+ -----------------------------------------------------------------------------*/
709
+
710
+ var header,
711
+ sections = options.header;
712
+ if (sections) {
713
+ header = $("<table class='fc-header'/>")
714
+ .append($("<tr/>")
715
+ .append($("<td class='fc-header-left'/>").append(buildSection(sections.left)))
716
+ .append($("<td class='fc-header-center'/>").append(buildSection(sections.center)))
717
+ .append($("<td class='fc-header-right'/>").append(buildSection(sections.right))))
718
+ .prependTo(element);
719
+ }
720
+ function buildSection(buttonStr) {
721
+ if (buttonStr) {
722
+ var tr = $("<tr/>");
723
+ $.each(buttonStr.split(' '), function(i) {
724
+ if (i > 0) {
725
+ tr.append("<td><span class='fc-header-space'/></td>");
726
+ }
727
+ var prevButton;
728
+ $.each(this.split(','), function(j, buttonName) {
729
+ if (buttonName == 'title') {
730
+ tr.append("<td><h2 class='fc-header-title'>&nbsp;</h2></td>");
731
+ if (prevButton) {
732
+ prevButton.addClass(tm + '-corner-right');
733
+ }
734
+ prevButton = null;
735
+ }else{
736
+ var buttonClick;
737
+ if (publicMethods[buttonName]) {
738
+ buttonClick = publicMethods[buttonName];
739
+ }
740
+ else if (views[buttonName]) {
741
+ buttonClick = function() {
742
+ button.removeClass(tm + '-state-hover');
743
+ changeView(buttonName);
744
+ };
745
+ }
746
+ if (buttonClick) {
747
+ if (prevButton) {
748
+ prevButton.addClass(tm + '-no-right');
749
+ }
750
+ var button,
751
+ icon = options.theme ? smartProperty(options.buttonIcons, buttonName) : null,
752
+ text = smartProperty(options.buttonText, buttonName);
753
+ if (icon) {
754
+ button = $("<div class='fc-button-" + buttonName + " ui-state-default'>" +
755
+ "<a><span class='ui-icon ui-icon-" + icon + "'/></a></div>");
756
+ }
757
+ else if (text) {
758
+ button = $("<div class='fc-button-" + buttonName + " " + tm + "-state-default'>" +
759
+ "<a><span>" + text + "</span></a></div>");
760
+ }
761
+ if (button) {
762
+ button
763
+ .click(function() {
764
+ if (!button.hasClass(tm + '-state-disabled')) {
765
+ buttonClick();
766
+ }
767
+ })
768
+ .mousedown(function() {
769
+ button
770
+ .not('.' + tm + '-state-active')
771
+ .not('.' + tm + '-state-disabled')
772
+ .addClass(tm + '-state-down');
773
+ })
774
+ .mouseup(function() {
775
+ button.removeClass(tm + '-state-down');
776
+ })
777
+ .hover(
778
+ function() {
779
+ button
780
+ .not('.' + tm + '-state-active')
781
+ .not('.' + tm + '-state-disabled')
782
+ .addClass(tm + '-state-hover');
783
+ },
784
+ function() {
785
+ button
786
+ .removeClass(tm + '-state-hover')
787
+ .removeClass(tm + '-state-down');
788
+ }
789
+ )
790
+ .appendTo($("<td/>").appendTo(tr));
791
+ if (prevButton) {
792
+ prevButton.addClass(tm + '-no-right');
793
+ }else{
794
+ button.addClass(tm + '-corner-left');
795
+ }
796
+ prevButton = button;
797
+ }
798
+ }
799
+ }
800
+ });
801
+ if (prevButton) {
802
+ prevButton.addClass(tm + '-corner-right');
803
+ }
804
+ });
805
+ return $("<table/>").append(tr);
806
+ }
807
+ }
808
+
809
+
810
+
811
+ /* Resizing
812
+ -----------------------------------------------------------------------------*/
813
+
814
+
815
+ function calcSize() {
816
+ if (options.contentHeight) {
817
+ suggestedViewHeight = options.contentHeight;
818
+ }
819
+ else if (options.height) {
820
+ suggestedViewHeight = options.height - (header ? header.height() : 0) - vsides(content[0]);
821
+ }
822
+ else {
823
+ suggestedViewHeight = Math.round(content.width() / Math.max(options.aspectRatio, .5));
824
+ }
825
+ }
826
+
827
+
828
+ function setSize(dateChanged) {
829
+ ignoreWindowResize++;
830
+ view.setHeight(suggestedViewHeight, dateChanged);
831
+ if (absoluteViewElement) {
832
+ absoluteViewElement.css('position', 'relative');
833
+ absoluteViewElement = null;
834
+ }
835
+ view.setWidth(content.width(), dateChanged);
836
+ ignoreWindowResize--;
837
+ }
838
+
839
+
840
+ function windowResize() {
841
+ if (!ignoreWindowResize) {
842
+ if (view.start) { // view has already been rendered
843
+ var uid = ++resizeUID;
844
+ setTimeout(function() { // add a delay
845
+ if (uid == resizeUID && !ignoreWindowResize && elementVisible()) {
846
+ if (elementOuterWidth != (elementOuterWidth = element.outerWidth())) {
847
+ ignoreWindowResize++; // in case the windowResize callback changes the height
848
+ sizeChanged();
849
+ view.trigger('windowResize', _element);
850
+ ignoreWindowResize--;
851
+ }
852
+ }
853
+ }, 200);
854
+ }else{
855
+ // calendar must have been initialized in a 0x0 iframe that has just been resized
856
+ lateRender();
857
+ }
858
+ }
859
+ }
860
+ $(window).resize(windowResize);
861
+
862
+
863
+ // let's begin...
864
+ changeView(options.defaultView);
865
+
866
+
867
+ // needed for IE in a 0x0 iframe, b/c when it is resized, never triggers a windowResize
868
+ if (!bodyVisible()) {
869
+ lateRender();
870
+ }
871
+
872
+
873
+ // called when we know the calendar couldn't be rendered when it was initialized,
874
+ // but we think it's ready now
875
+ function lateRender() {
876
+ setTimeout(function() { // IE7 needs this so dimensions are calculated correctly
877
+ if (!view.start && bodyVisible()) { // !view.start makes sure this never happens more than once
878
+ render();
879
+ }
880
+ },0);
881
+ }
882
+
883
+
884
+ });
885
+
886
+ return this;
887
+
888
+ };
889
+
890
+
891
+
892
+ /* Important Event Utilities
893
+ -----------------------------------------------------------------------------*/
894
+
895
+ var fakeID = 0;
896
+
897
+ function normalizeEvent(event, options) {
898
+ event._id = event._id || (event.id === undefined ? '_fc' + fakeID++ : event.id + '');
899
+ if (event.date) {
900
+ if (!event.start) {
901
+ event.start = event.date;
902
+ }
903
+ delete event.date;
904
+ }
905
+ event._start = cloneDate(event.start = parseDate(event.start));
906
+ event.end = parseDate(event.end);
907
+ if (event.end && event.end <= event.start) {
908
+ event.end = null;
909
+ }
910
+ event._end = event.end ? cloneDate(event.end) : null;
911
+ if (event.allDay === undefined) {
912
+ event.allDay = options.allDayDefault;
913
+ }
914
+ if (event.className) {
915
+ if (typeof event.className == 'string') {
916
+ event.className = event.className.split(/\s+/);
917
+ }
918
+ }else{
919
+ event.className = [];
920
+ }
921
+ }
922
+ // TODO: if there is no start date, return false to indicate an invalid event
923
+
924
+
925
+ /* Grid-based Views: month, basicWeek, basicDay
926
+ -----------------------------------------------------------------------------*/
927
+
928
+ setDefaults({
929
+ weekMode: 'fixed'
930
+ });
931
+
932
+ views.month = function(element, options) {
933
+ return new Grid(element, options, {
934
+ render: function(date, delta) {
935
+ if (delta) {
936
+ addMonths(date, delta);
937
+ date.setDate(1);
938
+ }
939
+ // start/end
940
+ var start = this.start = cloneDate(date, true);
941
+ start.setDate(1);
942
+ this.end = addMonths(cloneDate(start), 1);
943
+ // visStart/visEnd
944
+ var visStart = this.visStart = cloneDate(start),
945
+ visEnd = this.visEnd = cloneDate(this.end),
946
+ nwe = options.weekends ? 0 : 1;
947
+ if (nwe) {
948
+ skipWeekend(visStart);
949
+ skipWeekend(visEnd, -1, true);
950
+ }
951
+ addDays(visStart, -((visStart.getDay() - Math.max(options.firstDay, nwe) + 7) % 7));
952
+ addDays(visEnd, (7 - visEnd.getDay() + Math.max(options.firstDay, nwe)) % 7);
953
+ // row count
954
+ var rowCnt = Math.round((visEnd - visStart) / (DAY_MS * 7));
955
+ if (options.weekMode == 'fixed') {
956
+ addDays(visEnd, (6 - rowCnt) * 7);
957
+ rowCnt = 6;
958
+ }
959
+ // title
960
+ this.title = formatDate(
961
+ start,
962
+ this.option('titleFormat'),
963
+ options
964
+ );
965
+ // render
966
+ this.renderGrid(
967
+ rowCnt, options.weekends ? 7 : 5,
968
+ this.option('columnFormat'),
969
+ true
970
+ );
971
+ }
972
+ });
973
+ };
974
+
975
+ views.basicWeek = function(element, options) {
976
+ return new Grid(element, options, {
977
+ render: function(date, delta) {
978
+ if (delta) {
979
+ addDays(date, delta * 7);
980
+ }
981
+ var visStart = this.visStart = cloneDate(
982
+ this.start = addDays(cloneDate(date), -((date.getDay() - options.firstDay + 7) % 7))
983
+ ),
984
+ visEnd = this.visEnd = cloneDate(
985
+ this.end = addDays(cloneDate(visStart), 7)
986
+ );
987
+ if (!options.weekends) {
988
+ skipWeekend(visStart);
989
+ skipWeekend(visEnd, -1, true);
990
+ }
991
+ this.title = formatDates(
992
+ visStart,
993
+ addDays(cloneDate(visEnd), -1),
994
+ this.option('titleFormat'),
995
+ options
996
+ );
997
+ this.renderGrid(
998
+ 1, options.weekends ? 7 : 5,
999
+ this.option('columnFormat'),
1000
+ false
1001
+ );
1002
+ }
1003
+ });
1004
+ };
1005
+
1006
+ views.basicDay = function(element, options) {
1007
+ return new Grid(element, options, {
1008
+ render: function(date, delta) {
1009
+ if (delta) {
1010
+ addDays(date, delta);
1011
+ if (!options.weekends) {
1012
+ skipWeekend(date, delta < 0 ? -1 : 1);
1013
+ }
1014
+ }
1015
+ this.title = formatDate(date, this.option('titleFormat'), options);
1016
+ this.start = this.visStart = cloneDate(date, true);
1017
+ this.end = this.visEnd = addDays(cloneDate(this.start), 1);
1018
+ this.renderGrid(
1019
+ 1, 1,
1020
+ this.option('columnFormat'),
1021
+ false
1022
+ );
1023
+ }
1024
+ });
1025
+ };
1026
+
1027
+
1028
+ // rendering bugs
1029
+
1030
+ var tdHeightBug;
1031
+
1032
+
1033
+ function Grid(element, options, methods) {
1034
+
1035
+ var tm, firstDay,
1036
+ nwe, // no weekends (int)
1037
+ rtl, dis, dit, // day index sign / translate
1038
+ viewWidth, viewHeight,
1039
+ rowCnt, colCnt,
1040
+ colWidth,
1041
+ thead, tbody,
1042
+ cachedEvents=[],
1043
+ segmentContainer,
1044
+ dayContentPositions = new HorizontalPositionCache(function(dayOfWeek) {
1045
+ return tbody.find('td:eq(' + ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt) + ') div div');
1046
+ }),
1047
+ selectionManager,
1048
+ selectionMatrix,
1049
+ // ...
1050
+
1051
+ // initialize superclass
1052
+ view = $.extend(this, viewMethods, methods, {
1053
+ renderGrid: renderGrid,
1054
+ renderEvents: renderEvents,
1055
+ rerenderEvents: rerenderEvents,
1056
+ clearEvents: clearEvents,
1057
+ setHeight: setHeight,
1058
+ setWidth: setWidth,
1059
+ defaultEventEnd: function(event) { // calculates an end if event doesnt have one, mostly for resizing
1060
+ return cloneDate(event.start);
1061
+ }
1062
+ });
1063
+ view.init(element, options);
1064
+
1065
+
1066
+
1067
+ /* Grid Rendering
1068
+ -----------------------------------------------------------------------------*/
1069
+
1070
+
1071
+ disableTextSelection(element.addClass('fc-grid'));
1072
+
1073
+
1074
+ function renderGrid(r, c, colFormat, showNumbers) {
1075
+
1076
+ rowCnt = r;
1077
+ colCnt = c;
1078
+
1079
+ // update option-derived variables
1080
+ tm = options.theme ? 'ui' : 'fc';
1081
+ nwe = options.weekends ? 0 : 1;
1082
+ firstDay = options.firstDay;
1083
+ if (rtl = options.isRTL) {
1084
+ dis = -1;
1085
+ dit = colCnt - 1;
1086
+ }else{
1087
+ dis = 1;
1088
+ dit = 0;
1089
+ }
1090
+
1091
+ var month = view.start.getMonth(),
1092
+ today = clearTime(new Date()),
1093
+ s, i, j, d = cloneDate(view.visStart);
1094
+
1095
+ if (!tbody) { // first time, build all cells from scratch
1096
+
1097
+ var table = $("<table/>").appendTo(element);
1098
+
1099
+ s = "<thead><tr>";
1100
+ for (i=0; i<colCnt; i++) {
1101
+ s += "<th class='fc-" +
1102
+ dayIDs[d.getDay()] + ' ' + // needs to be first
1103
+ tm + '-state-default' +
1104
+ (i==dit ? ' fc-leftmost' : '') +
1105
+ "'>" + formatDate(d, colFormat, options) + "</th>";
1106
+ addDays(d, 1);
1107
+ if (nwe) {
1108
+ skipWeekend(d);
1109
+ }
1110
+ }
1111
+ thead = $(s + "</tr></thead>").appendTo(table);
1112
+
1113
+ s = "<tbody>";
1114
+ d = cloneDate(view.visStart);
1115
+ for (i=0; i<rowCnt; i++) {
1116
+ s += "<tr class='fc-week" + i + "'>";
1117
+ for (j=0; j<colCnt; j++) {
1118
+ s += "<td class='fc-" +
1119
+ dayIDs[d.getDay()] + ' ' + // needs to be first
1120
+ tm + '-state-default fc-day' + (i*colCnt+j) +
1121
+ (j==dit ? ' fc-leftmost' : '') +
1122
+ (rowCnt>1 && d.getMonth() != month ? ' fc-other-month' : '') +
1123
+ (+d == +today ?
1124
+ ' fc-today '+tm+'-state-highlight' :
1125
+ ' fc-not-today') + "'>" +
1126
+ (showNumbers ? "<div class='fc-day-number'>" + d.getDate() + "</div>" : '') +
1127
+ "<div class='fc-day-content'><div style='position:relative'>&nbsp;</div></div></td>";
1128
+ addDays(d, 1);
1129
+ if (nwe) {
1130
+ skipWeekend(d);
1131
+ }
1132
+ }
1133
+ s += "</tr>";
1134
+ }
1135
+ tbody = $(s + "</tbody>").appendTo(table);
1136
+ dayBind(tbody.find('td'));
1137
+
1138
+ segmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(element);
1139
+
1140
+ }else{ // NOT first time, reuse as many cells as possible
1141
+
1142
+ clearEvents();
1143
+
1144
+ var prevRowCnt = tbody.find('tr').length;
1145
+ if (rowCnt < prevRowCnt) {
1146
+ tbody.find('tr:gt(' + (rowCnt-1) + ')').remove(); // remove extra rows
1147
+ }
1148
+ else if (rowCnt > prevRowCnt) { // needs to create new rows...
1149
+ s = '';
1150
+ for (i=prevRowCnt; i<rowCnt; i++) {
1151
+ s += "<tr class='fc-week" + i + "'>";
1152
+ for (j=0; j<colCnt; j++) {
1153
+ s += "<td class='fc-" +
1154
+ dayIDs[d.getDay()] + ' ' + // needs to be first
1155
+ tm + '-state-default fc-new fc-day' + (i*colCnt+j) +
1156
+ (j==dit ? ' fc-leftmost' : '') + "'>" +
1157
+ (showNumbers ? "<div class='fc-day-number'></div>" : '') +
1158
+ "<div class='fc-day-content'><div style='position:relative'>&nbsp;</div></div>" +
1159
+ "</td>";
1160
+ addDays(d, 1);
1161
+ if (nwe) {
1162
+ skipWeekend(d);
1163
+ }
1164
+ }
1165
+ s += "</tr>";
1166
+ }
1167
+ tbody.append(s);
1168
+ }
1169
+ dayBind(tbody.find('td.fc-new').removeClass('fc-new'));
1170
+
1171
+ // re-label and re-class existing cells
1172
+ d = cloneDate(view.visStart);
1173
+ tbody.find('td').each(function() {
1174
+ var td = $(this);
1175
+ if (rowCnt > 1) {
1176
+ if (d.getMonth() == month) {
1177
+ td.removeClass('fc-other-month');
1178
+ }else{
1179
+ td.addClass('fc-other-month');
1180
+ }
1181
+ }
1182
+ if (+d == +today) {
1183
+ td.removeClass('fc-not-today')
1184
+ .addClass('fc-today')
1185
+ .addClass(tm + '-state-highlight');
1186
+ }else{
1187
+ td.addClass('fc-not-today')
1188
+ .removeClass('fc-today')
1189
+ .removeClass(tm + '-state-highlight');
1190
+ }
1191
+ td.find('div.fc-day-number').text(d.getDate());
1192
+ addDays(d, 1);
1193
+ if (nwe) {
1194
+ skipWeekend(d);
1195
+ }
1196
+ });
1197
+
1198
+ if (rowCnt == 1) { // more changes likely (week or day view)
1199
+
1200
+ // redo column header text and class
1201
+ d = cloneDate(view.visStart);
1202
+ thead.find('th').each(function() {
1203
+ $(this).text(formatDate(d, colFormat, options));
1204
+ this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
1205
+ addDays(d, 1);
1206
+ if (nwe) {
1207
+ skipWeekend(d);
1208
+ }
1209
+ });
1210
+
1211
+ // redo cell day-of-weeks
1212
+ d = cloneDate(view.visStart);
1213
+ tbody.find('td').each(function() {
1214
+ this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
1215
+ addDays(d, 1);
1216
+ if (nwe) {
1217
+ skipWeekend(d);
1218
+ }
1219
+ });
1220
+
1221
+ }
1222
+
1223
+ }
1224
+
1225
+ }
1226
+
1227
+
1228
+
1229
+ function setHeight(height) {
1230
+ viewHeight = height;
1231
+ var leftTDs = tbody.find('tr td:first-child'),
1232
+ tbodyHeight = viewHeight - thead.height(),
1233
+ rowHeight1, rowHeight2;
1234
+ if (options.weekMode == 'variable') {
1235
+ rowHeight1 = rowHeight2 = Math.floor(tbodyHeight / (rowCnt==1 ? 2 : 6));
1236
+ }else{
1237
+ rowHeight1 = Math.floor(tbodyHeight / rowCnt);
1238
+ rowHeight2 = tbodyHeight - rowHeight1*(rowCnt-1);
1239
+ }
1240
+ if (tdHeightBug === undefined) {
1241
+ // bug in firefox where cell height includes padding
1242
+ var tr = tbody.find('tr:first'),
1243
+ td = tr.find('td:first');
1244
+ td.height(rowHeight1);
1245
+ tdHeightBug = rowHeight1 != td.height();
1246
+ }
1247
+ if (tdHeightBug) {
1248
+ leftTDs.slice(0, -1).height(rowHeight1);
1249
+ leftTDs.slice(-1).height(rowHeight2);
1250
+ }else{
1251
+ setOuterHeight(leftTDs.slice(0, -1), rowHeight1);
1252
+ setOuterHeight(leftTDs.slice(-1), rowHeight2);
1253
+ }
1254
+ }
1255
+
1256
+
1257
+ function setWidth(width) {
1258
+ viewWidth = width;
1259
+ dayContentPositions.clear();
1260
+ setOuterWidth(
1261
+ thead.find('th').slice(0, -1),
1262
+ colWidth = Math.floor(viewWidth / colCnt)
1263
+ );
1264
+ }
1265
+
1266
+
1267
+
1268
+ /* Event Rendering
1269
+ -----------------------------------------------------------------------------*/
1270
+
1271
+
1272
+ function renderEvents(events) {
1273
+ view.reportEvents(cachedEvents = events);
1274
+ renderSegs(compileSegs(events));
1275
+ }
1276
+
1277
+
1278
+ function rerenderEvents(modifiedEventId) {
1279
+ clearEvents();
1280
+ renderSegs(compileSegs(cachedEvents), modifiedEventId);
1281
+ }
1282
+
1283
+
1284
+ function clearEvents() {
1285
+ view._clearEvents(); // only clears the hashes
1286
+ segmentContainer.empty();
1287
+ }
1288
+
1289
+
1290
+ function compileSegs(events) {
1291
+ var d1 = cloneDate(view.visStart),
1292
+ d2 = addDays(cloneDate(d1), colCnt),
1293
+ visEventsEnds = $.map(events, exclEndDay),
1294
+ i, row,
1295
+ j, level,
1296
+ k, seg,
1297
+ segs=[];
1298
+ for (i=0; i<rowCnt; i++) {
1299
+ row = stackSegs(view.sliceSegs(events, visEventsEnds, d1, d2));
1300
+ for (j=0; j<row.length; j++) {
1301
+ level = row[j];
1302
+ for (k=0; k<level.length; k++) {
1303
+ seg = level[k];
1304
+ seg.row = i;
1305
+ seg.level = j;
1306
+ segs.push(seg);
1307
+ }
1308
+ }
1309
+ addDays(d1, 7);
1310
+ addDays(d2, 7);
1311
+ }
1312
+ return segs;
1313
+ }
1314
+
1315
+
1316
+ function renderSegs(segs, modifiedEventId) {
1317
+ _renderDaySegs(
1318
+ segs,
1319
+ rowCnt,
1320
+ view,
1321
+ 0,
1322
+ viewWidth,
1323
+ function(i) { return tbody.find('tr:eq('+i+')') },
1324
+ dayContentPositions.left,
1325
+ dayContentPositions.right,
1326
+ segmentContainer,
1327
+ bindSegHandlers,
1328
+ modifiedEventId
1329
+ );
1330
+ }
1331
+
1332
+
1333
+ function bindSegHandlers(event, eventElement, seg) {
1334
+ view.eventElementHandlers(event, eventElement);
1335
+ if (event.editable || event.editable === undefined && options.editable) {
1336
+ draggableEvent(event, eventElement);
1337
+ if (seg.isEnd) {
1338
+ view.resizableDayEvent(event, eventElement, colWidth);
1339
+ }
1340
+ }
1341
+ }
1342
+
1343
+
1344
+
1345
+ /* Event Dragging
1346
+ -----------------------------------------------------------------------------*/
1347
+
1348
+
1349
+ function draggableEvent(event, eventElement) {
1350
+ if (!options.disableDragging && eventElement.draggable) {
1351
+ var matrix,
1352
+ dayDelta = 0;
1353
+ eventElement.draggable({
1354
+ zIndex: 9,
1355
+ delay: 50,
1356
+ opacity: view.option('dragOpacity'),
1357
+ revertDuration: options.dragRevertDuration,
1358
+ start: function(ev, ui) {
1359
+ view.hideEvents(event, eventElement);
1360
+ view.trigger('eventDragStart', eventElement, event, ev, ui);
1361
+ matrix = buildDayMatrix(function(cell) {
1362
+ eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta);
1363
+ clearOverlays();
1364
+ if (cell) {
1365
+ dayDelta = cell.rowDelta*7 + cell.colDelta*dis;
1366
+ renderDayOverlays(
1367
+ matrix,
1368
+ addDays(cloneDate(event.start), dayDelta),
1369
+ addDays(exclEndDay(event), dayDelta)
1370
+ );
1371
+ }else{
1372
+ dayDelta = 0;
1373
+ }
1374
+ });
1375
+ matrix.mouse(ev);
1376
+ },
1377
+ drag: function(ev) {
1378
+ matrix.mouse(ev);
1379
+ },
1380
+ stop: function(ev, ui) {
1381
+ clearOverlays();
1382
+ view.trigger('eventDragStop', eventElement, event, ev, ui);
1383
+ if (dayDelta) {
1384
+ eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
1385
+ view.eventDrop(this, event, dayDelta, 0, event.allDay, ev, ui);
1386
+ }else{
1387
+ if ($.browser.msie) {
1388
+ eventElement.css('filter', ''); // clear IE opacity side-effects
1389
+ }
1390
+ view.showEvents(event, eventElement);
1391
+ }
1392
+ }
1393
+ });
1394
+ }
1395
+ }
1396
+
1397
+
1398
+
1399
+ /* Day clicking and binding
1400
+ ---------------------------------------------------------*/
1401
+
1402
+ function dayBind(days) {
1403
+ days.click(dayClick)
1404
+ .mousedown(selectionMousedown);
1405
+ }
1406
+
1407
+ function dayClick(ev) {
1408
+ if (!view.option('selectable')) { // SelectionManager will worry about dayClick
1409
+ var n = parseInt(this.className.match(/fc\-day(\d+)/)[1]),
1410
+ date = addDays(
1411
+ cloneDate(view.visStart),
1412
+ Math.floor(n/colCnt) * 7 + n % colCnt
1413
+ );
1414
+ // TODO: what about weekends in middle of week?
1415
+ view.trigger('dayClick', this, date, true, ev);
1416
+ }
1417
+ }
1418
+
1419
+
1420
+
1421
+ /* Selecting
1422
+ --------------------------------------------------------*/
1423
+
1424
+ selectionManager = new SelectionManager(
1425
+ view,
1426
+ unselect,
1427
+ function(startDate, endDate, allDay) {
1428
+ renderDayOverlays(
1429
+ selectionMatrix,
1430
+ startDate,
1431
+ addDays(cloneDate(endDate), 1)
1432
+ );
1433
+ },
1434
+ clearOverlays
1435
+ );
1436
+
1437
+ function selectionMousedown(ev) {
1438
+ if (view.option('selectable')) {
1439
+ selectionMatrix = buildDayMatrix(function(cell) {
1440
+ if (cell) {
1441
+ var d = cellDate(cell.row, cell.col);
1442
+ selectionManager.drag(d, d, true);
1443
+ }else{
1444
+ selectionManager.drag();
1445
+ }
1446
+ });
1447
+ documentDragHelp(
1448
+ function(ev) {
1449
+ selectionMatrix.mouse(ev);
1450
+ },
1451
+ function(ev) {
1452
+ selectionManager.dragStop(ev);
1453
+ }
1454
+ );
1455
+ selectionManager.dragStart(ev);
1456
+ selectionMatrix.mouse(ev);
1457
+ return false; // prevent auto-unselect and text selection
1458
+ }
1459
+ }
1460
+
1461
+ documentUnselectAuto(view, unselect);
1462
+
1463
+ view.select = function(start, end, allDay) {
1464
+ if (!end) {
1465
+ end = cloneDate(start);
1466
+ }
1467
+ selectionMatrix = buildDayMatrix();
1468
+ selectionManager.select(start, end, allDay);
1469
+ };
1470
+
1471
+ function unselect() {
1472
+ selectionManager.unselect();
1473
+ }
1474
+ view.unselect = unselect;
1475
+
1476
+
1477
+
1478
+
1479
+ /* Semi-transparent Overlay Helpers
1480
+ ------------------------------------------------------*/
1481
+
1482
+ function renderDayOverlays(matrix, overlayStart, overlayEnd) { // overlayEnd is exclusive
1483
+ var rowStart = cloneDate(view.visStart);
1484
+ var rowEnd = addDays(cloneDate(rowStart), colCnt);
1485
+ for (var i=0; i<rowCnt; i++) {
1486
+ var stretchStart = new Date(Math.max(rowStart, overlayStart));
1487
+ var stretchEnd = new Date(Math.min(rowEnd, overlayEnd));
1488
+ if (stretchStart < stretchEnd) {
1489
+ var colStart, colEnd;
1490
+ if (rtl) {
1491
+ colStart = dayDiff(stretchEnd, rowStart)*dis+dit+1;
1492
+ colEnd = dayDiff(stretchStart, rowStart)*dis+dit+1;
1493
+ }else{
1494
+ colStart = dayDiff(stretchStart, rowStart);
1495
+ colEnd = dayDiff(stretchEnd, rowStart);
1496
+ }
1497
+ var rect = matrix.rect(i, colStart, i+1, colEnd, element);
1498
+ dayBind(
1499
+ view.renderOverlay(rect, element)
1500
+ );
1501
+ }
1502
+ addDays(rowStart, 7);
1503
+ addDays(rowEnd, 7);
1504
+ }
1505
+ }
1506
+
1507
+ function clearOverlays() {
1508
+ view.clearOverlays();
1509
+ }
1510
+
1511
+
1512
+
1513
+
1514
+ /* Utils
1515
+ ---------------------------------------------------*/
1516
+
1517
+
1518
+ function buildDayMatrix(changeCallback) {
1519
+ var tds = tbody.find('tr:first td');
1520
+ if (rtl) {
1521
+ tds = $(tds.get().reverse());
1522
+ }
1523
+ return new HoverMatrix(tbody.find('tr'), tds, changeCallback);
1524
+ }
1525
+
1526
+
1527
+ function cellDate(r, c) { // convert r,c to date
1528
+ return addDays(cloneDate(view.visStart), r*7 + c*dis+dit);
1529
+ // TODO: what about weekends in middle of week?
1530
+ }
1531
+
1532
+
1533
+ }
1534
+
1535
+
1536
+ function _renderDaySegs(segs, rowCnt, view, minLeft, maxLeft, getRow, dayContentLeft, dayContentRight, segmentContainer, bindSegHandlers, modifiedEventId) {
1537
+
1538
+ var options=view.options,
1539
+ rtl=options.isRTL,
1540
+ i, segCnt=segs.length, seg,
1541
+ event,
1542
+ className,
1543
+ left, right,
1544
+ html='',
1545
+ eventElements,
1546
+ eventElement,
1547
+ triggerRes,
1548
+ hsideCache={},
1549
+ vmarginCache={},
1550
+ key, val,
1551
+ rowI, top, levelI, levelHeight,
1552
+ rowDivs=[],
1553
+ rowDivTops=[];
1554
+
1555
+ // calculate desired position/dimensions, create html
1556
+ for (i=0; i<segCnt; i++) {
1557
+ seg = segs[i];
1558
+ event = seg.event;
1559
+ className = 'fc-event fc-event-hori ';
1560
+ if (rtl) {
1561
+ if (seg.isStart) {
1562
+ className += 'fc-corner-right ';
1563
+ }
1564
+ if (seg.isEnd) {
1565
+ className += 'fc-corner-left ';
1566
+ }
1567
+ left = seg.isEnd ? dayContentLeft(seg.end.getDay()-1) : minLeft;
1568
+ right = seg.isStart ? dayContentRight(seg.start.getDay()) : maxLeft;
1569
+ }else{
1570
+ if (seg.isStart) {
1571
+ className += 'fc-corner-left ';
1572
+ }
1573
+ if (seg.isEnd) {
1574
+ className += 'fc-corner-right ';
1575
+ }
1576
+ left = seg.isStart ? dayContentLeft(seg.start.getDay()) : minLeft;
1577
+ right = seg.isEnd ? dayContentRight(seg.end.getDay()-1) : maxLeft;
1578
+ }
1579
+ html +=
1580
+ "<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;left:"+left+"px'>" +
1581
+ "<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + ">" +
1582
+ (!event.allDay && seg.isStart ?
1583
+ "<span class='fc-event-time'>" +
1584
+ htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'), options)) +
1585
+ "</span>"
1586
+ :'') +
1587
+ "<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
1588
+ "</a>" +
1589
+ ((event.editable || event.editable === undefined && options.editable) && !options.disableResizing && $.fn.resizable ?
1590
+ "<div class='ui-resizable-handle ui-resizable-" + (rtl ? 'w' : 'e') + "'></div>"
1591
+ : '') +
1592
+ "</div>";
1593
+ seg.left = left;
1594
+ seg.outerWidth = right - left;
1595
+ }
1596
+ segmentContainer[0].innerHTML = html; // faster than html()
1597
+ eventElements = segmentContainer.children();
1598
+
1599
+ // retrieve elements, run through eventRender callback, bind handlers
1600
+ for (i=0; i<segCnt; i++) {
1601
+ seg = segs[i];
1602
+ eventElement = $(eventElements[i]); // faster than eq()
1603
+ event = seg.event;
1604
+ triggerRes = view.trigger('eventRender', event, event, eventElement);
1605
+ if (triggerRes === false) {
1606
+ eventElement.remove();
1607
+ }else{
1608
+ if (triggerRes && triggerRes !== true) {
1609
+ eventElement.remove();
1610
+ eventElement = $(triggerRes)
1611
+ .css({
1612
+ position: 'absolute',
1613
+ left: seg.left
1614
+ })
1615
+ .appendTo(segmentContainer);
1616
+ }
1617
+ seg.element = eventElement;
1618
+ if (event._id === modifiedEventId) {
1619
+ bindSegHandlers(event, eventElement, seg);
1620
+ }else{
1621
+ eventElement[0]._fci = i; // for lazySegBind
1622
+ }
1623
+ view.reportEventElement(event, eventElement);
1624
+ }
1625
+ }
1626
+
1627
+ lazySegBind(segmentContainer, segs, bindSegHandlers);
1628
+
1629
+ // record event horizontal sides
1630
+ for (i=0; i<segCnt; i++) {
1631
+ seg = segs[i];
1632
+ if (eventElement = seg.element) {
1633
+ val = hsideCache[key = seg.key = cssKey(eventElement[0])];
1634
+ seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement[0], true)) : val;
1635
+ }
1636
+ }
1637
+
1638
+ // set event widths
1639
+ for (i=0; i<segCnt; i++) {
1640
+ seg = segs[i];
1641
+ if (eventElement = seg.element) {
1642
+ eventElement[0].style.width = seg.outerWidth - seg.hsides + 'px';
1643
+ }
1644
+ }
1645
+
1646
+ // record event heights
1647
+ for (i=0; i<segCnt; i++) {
1648
+ seg = segs[i];
1649
+ if (eventElement = seg.element) {
1650
+ val = vmarginCache[key = seg.key];
1651
+ seg.outerHeight = eventElement[0].offsetHeight + (
1652
+ val === undefined ? (vmarginCache[key] = vmargins(eventElement[0])) : val
1653
+ );
1654
+ }
1655
+ }
1656
+
1657
+ // set row heights, calculate event tops (in relation to row top)
1658
+ for (i=0, rowI=0; rowI<rowCnt; rowI++) {
1659
+ top = levelI = levelHeight = 0;
1660
+ while (i<segCnt && (seg = segs[i]).row == rowI) {
1661
+ if (seg.level != levelI) {
1662
+ top += levelHeight;
1663
+ levelHeight = 0;
1664
+ levelI++;
1665
+ }
1666
+ levelHeight = Math.max(levelHeight, seg.outerHeight||0);
1667
+ seg.top = top;
1668
+ i++;
1669
+ }
1670
+ rowDivs[rowI] = getRow(rowI).find('td:first div.fc-day-content > div') // optimal selector?
1671
+ .height(top + levelHeight);
1672
+ }
1673
+
1674
+ // calculate row tops
1675
+ for (rowI=0; rowI<rowCnt; rowI++) {
1676
+ rowDivTops[rowI] = rowDivs[rowI][0].offsetTop;
1677
+ }
1678
+
1679
+ // set event tops
1680
+ for (i=0; i<segCnt; i++) {
1681
+ seg = segs[i];
1682
+ if (eventElement = seg.element) {
1683
+ eventElement[0].style.top = rowDivTops[seg.row] + seg.top + 'px';
1684
+ event = seg.event;
1685
+ view.trigger('eventAfterRender', event, event, eventElement);
1686
+ }
1687
+ }
1688
+
1689
+ }
1690
+
1691
+
1692
+
1693
+ /* Agenda Views: agendaWeek/agendaDay
1694
+ -----------------------------------------------------------------------------*/
1695
+
1696
+ setDefaults({
1697
+ allDaySlot: true,
1698
+ allDayText: 'all-day',
1699
+ firstHour: 6,
1700
+ slotMinutes: 30,
1701
+ defaultEventMinutes: 120,
1702
+ axisFormat: 'h(:mm)tt',
1703
+ timeFormat: {
1704
+ agenda: 'h:mm{ - h:mm}'
1705
+ },
1706
+ dragOpacity: {
1707
+ agenda: .5
1708
+ },
1709
+ minTime: 0,
1710
+ maxTime: 24
1711
+ });
1712
+
1713
+ views.agendaWeek = function(element, options) {
1714
+ return new Agenda(element, options, {
1715
+ render: function(date, delta) {
1716
+ if (delta) {
1717
+ addDays(date, delta * 7);
1718
+ }
1719
+ var visStart = this.visStart = cloneDate(
1720
+ this.start = addDays(cloneDate(date), -((date.getDay() - options.firstDay + 7) % 7))
1721
+ ),
1722
+ visEnd = this.visEnd = cloneDate(
1723
+ this.end = addDays(cloneDate(visStart), 7)
1724
+ );
1725
+ if (!options.weekends) {
1726
+ skipWeekend(visStart);
1727
+ skipWeekend(visEnd, -1, true);
1728
+ }
1729
+ this.title = formatDates(
1730
+ visStart,
1731
+ addDays(cloneDate(visEnd), -1),
1732
+ this.option('titleFormat'),
1733
+ options
1734
+ );
1735
+ this.renderAgenda(
1736
+ options.weekends ? 7 : 5,
1737
+ this.option('columnFormat')
1738
+ );
1739
+ }
1740
+ });
1741
+ };
1742
+
1743
+ views.agendaDay = function(element, options) {
1744
+ return new Agenda(element, options, {
1745
+ render: function(date, delta) {
1746
+ if (delta) {
1747
+ addDays(date, delta);
1748
+ if (!options.weekends) {
1749
+ skipWeekend(date, delta < 0 ? -1 : 1);
1750
+ }
1751
+ }
1752
+ this.title = formatDate(date, this.option('titleFormat'), options);
1753
+ this.start = this.visStart = cloneDate(date, true);
1754
+ this.end = this.visEnd = addDays(cloneDate(this.start), 1);
1755
+ this.renderAgenda(
1756
+ 1,
1757
+ this.option('columnFormat')
1758
+ );
1759
+ }
1760
+ });
1761
+ };
1762
+
1763
+ function Agenda(element, options, methods) {
1764
+
1765
+ var head, body, bodyContent, bodyTable, bg,
1766
+ colCnt,
1767
+ slotCnt=0, // spanning all the way across
1768
+ axisWidth, colWidth, slotHeight,
1769
+ viewWidth, viewHeight,
1770
+ savedScrollTop,
1771
+ cachedEvents=[],
1772
+ daySegmentContainer,
1773
+ slotSegmentContainer,
1774
+ tm, firstDay,
1775
+ nwe, // no weekends (int)
1776
+ rtl, dis, dit, // day index sign / translate
1777
+ minMinute, maxMinute,
1778
+ colContentPositions = new HorizontalPositionCache(function(col) {
1779
+ return bg.find('td:eq(' + col + ') div div');
1780
+ }),
1781
+ slotTopCache = {},
1782
+ daySelectionManager,
1783
+ slotSelectionManager,
1784
+ selectionHelper,
1785
+ selectionMatrix,
1786
+ // ...
1787
+
1788
+ view = $.extend(this, viewMethods, methods, {
1789
+ renderAgenda: renderAgenda,
1790
+ renderEvents: renderEvents,
1791
+ rerenderEvents: rerenderEvents,
1792
+ clearEvents: clearEvents,
1793
+ setHeight: setHeight,
1794
+ setWidth: setWidth,
1795
+ beforeHide: function() {
1796
+ savedScrollTop = body.scrollTop();
1797
+ },
1798
+ afterShow: function() {
1799
+ body.scrollTop(savedScrollTop);
1800
+ },
1801
+ defaultEventEnd: function(event) {
1802
+ var start = cloneDate(event.start);
1803
+ if (event.allDay) {
1804
+ return start;
1805
+ }
1806
+ return addMinutes(start, options.defaultEventMinutes);
1807
+ }
1808
+ });
1809
+ view.init(element, options);
1810
+
1811
+
1812
+
1813
+ /* Time-slot rendering
1814
+ -----------------------------------------------------------------------------*/
1815
+
1816
+
1817
+ disableTextSelection(element.addClass('fc-agenda'));
1818
+
1819
+
1820
+ function renderAgenda(c, colFormat) {
1821
+
1822
+ colCnt = c;
1823
+
1824
+ // update option-derived variables
1825
+ tm = options.theme ? 'ui' : 'fc';
1826
+ nwe = options.weekends ? 0 : 1;
1827
+ firstDay = options.firstDay;
1828
+ if (rtl = options.isRTL) {
1829
+ dis = -1;
1830
+ dit = colCnt - 1;
1831
+ }else{
1832
+ dis = 1;
1833
+ dit = 0;
1834
+ }
1835
+ minMinute = parseTime(options.minTime);
1836
+ maxMinute = parseTime(options.maxTime);
1837
+
1838
+ var d0 = rtl ? addDays(cloneDate(view.visEnd), -1) : cloneDate(view.visStart),
1839
+ d = cloneDate(d0),
1840
+ today = clearTime(new Date());
1841
+
1842
+ if (!head) { // first time rendering, build from scratch
1843
+
1844
+ var i,
1845
+ minutes,
1846
+ slotNormal = options.slotMinutes % 15 == 0, //...
1847
+
1848
+ // head
1849
+ s = "<div class='fc-agenda-head' style='position:relative;z-index:4'>" +
1850
+ "<table style='width:100%'>" +
1851
+ "<tr class='fc-first" + (options.allDaySlot ? '' : ' fc-last') + "'>" +
1852
+ "<th class='fc-leftmost " +
1853
+ tm + "-state-default'>&nbsp;</th>";
1854
+ for (i=0; i<colCnt; i++) {
1855
+ s += "<th class='fc-" +
1856
+ dayIDs[d.getDay()] + ' ' + // needs to be first
1857
+ tm + '-state-default' +
1858
+ "'>" + formatDate(d, colFormat, options) + "</th>";
1859
+ addDays(d, dis);
1860
+ if (nwe) {
1861
+ skipWeekend(d, dis);
1862
+ }
1863
+ }
1864
+ s += "<th class='" + tm + "-state-default'>&nbsp;</th></tr>";
1865
+ if (options.allDaySlot) {
1866
+ s += "<tr class='fc-all-day'>" +
1867
+ "<th class='fc-axis fc-leftmost " + tm + "-state-default'>" + options.allDayText + "</th>" +
1868
+ "<td colspan='" + colCnt + "' class='" + tm + "-state-default'>" +
1869
+ "<div class='fc-day-content'><div style='position:relative'>&nbsp;</div></div></td>" +
1870
+ "<th class='" + tm + "-state-default'>&nbsp;</th>" +
1871
+ "</tr><tr class='fc-divider fc-last'><th colspan='" + (colCnt+2) + "' class='" +
1872
+ tm + "-state-default fc-leftmost'><div/></th></tr>";
1873
+ }
1874
+ s+= "</table></div>";
1875
+ head = $(s).appendTo(element);
1876
+ dayBind(head.find('td'));
1877
+
1878
+ // all-day event container
1879
+ daySegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(head);
1880
+
1881
+ // body
1882
+ d = zeroDate();
1883
+ var maxd = addMinutes(cloneDate(d), maxMinute);
1884
+ addMinutes(d, minMinute);
1885
+ s = "<table>";
1886
+ for (i=0; d < maxd; i++) {
1887
+ minutes = d.getMinutes();
1888
+ s += "<tr class='" +
1889
+ (!i ? 'fc-first' : (!minutes ? '' : 'fc-minor')) +
1890
+ "'><th class='fc-axis fc-leftmost " + tm + "-state-default'>" +
1891
+ ((!slotNormal || !minutes) ? formatDate(d, options.axisFormat) : '&nbsp;') +
1892
+ "</th><td class='fc-slot" + i + ' ' +
1893
+ tm + "-state-default'><div style='position:relative'>&nbsp;</div></td></tr>";
1894
+ addMinutes(d, options.slotMinutes);
1895
+ slotCnt++;
1896
+ }
1897
+ s += "</table>";
1898
+ body = $("<div class='fc-agenda-body' style='position:relative;z-index:2;overflow:auto'/>")
1899
+ .append(bodyContent = $("<div style='position:relative;overflow:hidden'>")
1900
+ .append(bodyTable = $(s)))
1901
+ .appendTo(element);
1902
+ slotBind(body.find('td'));
1903
+
1904
+ // slot event container
1905
+ slotSegmentContainer = $("<div style='position:absolute;z-index:8;top:0;left:0'/>").appendTo(bodyContent);
1906
+
1907
+ // background stripes
1908
+ d = cloneDate(d0);
1909
+ s = "<div class='fc-agenda-bg' style='position:absolute;z-index:1'>" +
1910
+ "<table style='width:100%;height:100%'><tr class='fc-first'>";
1911
+ for (i=0; i<colCnt; i++) {
1912
+ s += "<td class='fc-" +
1913
+ dayIDs[d.getDay()] + ' ' + // needs to be first
1914
+ tm + '-state-default ' +
1915
+ (!i ? 'fc-leftmost ' : '') +
1916
+ (+d == +today ? tm + '-state-highlight fc-today' : 'fc-not-today') +
1917
+ "'><div class='fc-day-content'><div>&nbsp;</div></div></td>";
1918
+ addDays(d, dis);
1919
+ if (nwe) {
1920
+ skipWeekend(d, dis);
1921
+ }
1922
+ }
1923
+ s += "</tr></table></div>";
1924
+ bg = $(s).appendTo(element);
1925
+
1926
+ }else{ // skeleton already built, just modify it
1927
+
1928
+ clearEvents();
1929
+
1930
+ // redo column header text and class
1931
+ head.find('tr:first th').slice(1, -1).each(function() {
1932
+ $(this).text(formatDate(d, colFormat, options));
1933
+ this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
1934
+ addDays(d, dis);
1935
+ if (nwe) {
1936
+ skipWeekend(d, dis);
1937
+ }
1938
+ });
1939
+
1940
+ // change classes of background stripes
1941
+ d = cloneDate(d0);
1942
+ bg.find('td').each(function() {
1943
+ this.className = this.className.replace(/^fc-\w+(?= )/, 'fc-' + dayIDs[d.getDay()]);
1944
+ if (+d == +today) {
1945
+ $(this)
1946
+ .removeClass('fc-not-today')
1947
+ .addClass('fc-today')
1948
+ .addClass(tm + '-state-highlight');
1949
+ }else{
1950
+ $(this)
1951
+ .addClass('fc-not-today')
1952
+ .removeClass('fc-today')
1953
+ .removeClass(tm + '-state-highlight');
1954
+ }
1955
+ addDays(d, dis);
1956
+ if (nwe) {
1957
+ skipWeekend(d, dis);
1958
+ }
1959
+ });
1960
+
1961
+ }
1962
+
1963
+ }
1964
+
1965
+
1966
+ function resetScroll() {
1967
+ var d0 = zeroDate(),
1968
+ scrollDate = cloneDate(d0);
1969
+ scrollDate.setHours(options.firstHour);
1970
+ var top = timePosition(d0, scrollDate) + 1, // +1 for the border
1971
+ scroll = function() {
1972
+ body.scrollTop(top);
1973
+ };
1974
+ scroll();
1975
+ setTimeout(scroll, 0); // overrides any previous scroll state made by the browser
1976
+ }
1977
+
1978
+
1979
+ function setHeight(height, dateChanged) {
1980
+ viewHeight = height;
1981
+ slotTopCache = {};
1982
+
1983
+ body.height(height - head.height());
1984
+
1985
+ slotHeight = body.find('tr:first div').height() + 1;
1986
+
1987
+ bg.css({
1988
+ top: head.find('tr').height(),
1989
+ height: height
1990
+ });
1991
+
1992
+ if (dateChanged) {
1993
+ resetScroll();
1994
+ }
1995
+ }
1996
+
1997
+
1998
+ function setWidth(width) {
1999
+ viewWidth = width;
2000
+ colContentPositions.clear();
2001
+
2002
+ body.width(width);
2003
+ bodyTable.width('');
2004
+
2005
+ var topTDs = head.find('tr:first th'),
2006
+ stripeTDs = bg.find('td'),
2007
+ clientWidth = body[0].clientWidth;
2008
+
2009
+ bodyTable.width(clientWidth);
2010
+
2011
+ // time-axis width
2012
+ axisWidth = 0;
2013
+ setOuterWidth(
2014
+ head.find('tr:lt(2) th:first').add(body.find('tr:first th'))
2015
+ .width('')
2016
+ .each(function() {
2017
+ axisWidth = Math.max(axisWidth, $(this).outerWidth());
2018
+ }),
2019
+ axisWidth
2020
+ );
2021
+
2022
+ // column width
2023
+ colWidth = Math.floor((clientWidth - axisWidth) / colCnt);
2024
+ setOuterWidth(stripeTDs.slice(0, -1), colWidth);
2025
+ setOuterWidth(topTDs.slice(1, -2), colWidth);
2026
+ setOuterWidth(topTDs.slice(-2, -1), clientWidth - axisWidth - colWidth*(colCnt-1));
2027
+
2028
+ bg.css({
2029
+ left: axisWidth,
2030
+ width: clientWidth - axisWidth
2031
+ });
2032
+ }
2033
+
2034
+
2035
+
2036
+ /* Slot/Day clicking and binding
2037
+ -----------------------------------------------------------------------*/
2038
+
2039
+
2040
+ function dayBind(tds) {
2041
+ tds.click(slotClick)
2042
+ .mousedown(daySelectionMousedown);
2043
+ }
2044
+
2045
+
2046
+ function slotBind(tds) {
2047
+ tds.click(slotClick)
2048
+ .mousedown(slotSelectionMousedown);
2049
+ }
2050
+
2051
+
2052
+ function slotClick(ev) {
2053
+ if (!view.option('selectable')) { // SelectionManager will worry about dayClick
2054
+ var col = Math.floor((ev.pageX - bg.offset().left) / colWidth),
2055
+ date = addDays(cloneDate(view.visStart), dit + dis*col),
2056
+ rowMatch = this.className.match(/fc-slot(\d+)/);
2057
+ if (rowMatch) {
2058
+ var mins = parseInt(rowMatch[1]) * options.slotMinutes,
2059
+ hours = Math.floor(mins/60);
2060
+ date.setHours(hours);
2061
+ date.setMinutes(mins%60 + minMinute);
2062
+ view.trigger('dayClick', this, date, false, ev);
2063
+ }else{
2064
+ view.trigger('dayClick', this, date, true, ev);
2065
+ }
2066
+ }
2067
+ }
2068
+
2069
+
2070
+
2071
+ /* Event Rendering
2072
+ -----------------------------------------------------------------------------*/
2073
+
2074
+ function renderEvents(events, modifiedEventId) {
2075
+ view.reportEvents(cachedEvents = events);
2076
+ var i, len=events.length,
2077
+ dayEvents=[],
2078
+ slotEvents=[];
2079
+ for (i=0; i<len; i++) {
2080
+ if (events[i].allDay) {
2081
+ dayEvents.push(events[i]);
2082
+ }else{
2083
+ slotEvents.push(events[i]);
2084
+ }
2085
+ }
2086
+ renderDaySegs(compileDaySegs(dayEvents), modifiedEventId);
2087
+ renderSlotSegs(compileSlotSegs(slotEvents), modifiedEventId);
2088
+ }
2089
+
2090
+
2091
+ function rerenderEvents(modifiedEventId) {
2092
+ clearEvents();
2093
+ renderEvents(cachedEvents, modifiedEventId);
2094
+ }
2095
+
2096
+
2097
+ function clearEvents() {
2098
+ view._clearEvents(); // only clears the hashes
2099
+ daySegmentContainer.empty();
2100
+ slotSegmentContainer.empty();
2101
+ }
2102
+
2103
+
2104
+
2105
+
2106
+
2107
+ function compileDaySegs(events) {
2108
+ var levels = stackSegs(view.sliceSegs(events, $.map(events, exclEndDay), view.visStart, view.visEnd)),
2109
+ i, levelCnt=levels.length, level,
2110
+ j, seg,
2111
+ segs=[];
2112
+ for (i=0; i<levelCnt; i++) {
2113
+ level = levels[i];
2114
+ for (j=0; j<level.length; j++) {
2115
+ seg = level[j];
2116
+ seg.row = 0;
2117
+ seg.level = i;
2118
+ segs.push(seg);
2119
+ }
2120
+ }
2121
+ return segs;
2122
+ }
2123
+
2124
+
2125
+ function compileSlotSegs(events) {
2126
+ var d = addMinutes(cloneDate(view.visStart), minMinute),
2127
+ visEventEnds = $.map(events, slotEventEnd),
2128
+ i, col,
2129
+ j, level,
2130
+ k, seg,
2131
+ segs=[];
2132
+ for (i=0; i<colCnt; i++) {
2133
+ col = stackSegs(view.sliceSegs(events, visEventEnds, d, addMinutes(cloneDate(d), maxMinute-minMinute)));
2134
+ countForwardSegs(col);
2135
+ for (j=0; j<col.length; j++) {
2136
+ level = col[j];
2137
+ for (k=0; k<level.length; k++) {
2138
+ seg = level[k];
2139
+ seg.col = i;
2140
+ seg.level = j;
2141
+ segs.push(seg);
2142
+ }
2143
+ }
2144
+ addDays(d, 1, true);
2145
+ }
2146
+ return segs;
2147
+ }
2148
+
2149
+
2150
+
2151
+
2152
+ // renders 'all-day' events at the top
2153
+
2154
+ function renderDaySegs(segs, modifiedEventId) {
2155
+ if (options.allDaySlot) {
2156
+ _renderDaySegs(
2157
+ segs,
2158
+ 1,
2159
+ view,
2160
+ axisWidth,
2161
+ viewWidth,
2162
+ function() {
2163
+ return head.find('tr.fc-all-day');
2164
+ },
2165
+ function(dayOfWeek) {
2166
+ return axisWidth + colContentPositions.left(dayOfWeekCol(dayOfWeek));
2167
+ },
2168
+ function(dayOfWeek) {
2169
+ return axisWidth + colContentPositions.right(dayOfWeekCol(dayOfWeek));
2170
+ },
2171
+ daySegmentContainer,
2172
+ daySegBind,
2173
+ modifiedEventId
2174
+ );
2175
+ setHeight(viewHeight); // might have pushed the body down, so resize
2176
+ }
2177
+ }
2178
+
2179
+
2180
+
2181
+ // renders events in the 'time slots' at the bottom
2182
+
2183
+ function renderSlotSegs(segs, modifiedEventId) {
2184
+
2185
+ var i, segCnt=segs.length, seg,
2186
+ event,
2187
+ className,
2188
+ top, bottom,
2189
+ colI, levelI, forward,
2190
+ leftmost,
2191
+ availWidth,
2192
+ outerWidth,
2193
+ left,
2194
+ html='',
2195
+ eventElements,
2196
+ eventElement,
2197
+ triggerRes,
2198
+ vsideCache={},
2199
+ hsideCache={},
2200
+ key, val,
2201
+ titleSpan,
2202
+ height;
2203
+
2204
+ // calculate position/dimensions, create html
2205
+ for (i=0; i<segCnt; i++) {
2206
+ seg = segs[i];
2207
+ event = seg.event;
2208
+ className = 'fc-event fc-event-vert ';
2209
+ if (seg.isStart) {
2210
+ className += 'fc-corner-top ';
2211
+ }
2212
+ if (seg.isEnd) {
2213
+ className += 'fc-corner-bottom ';
2214
+ }
2215
+ top = timePosition(seg.start, seg.start);
2216
+ bottom = timePosition(seg.start, seg.end);
2217
+ colI = seg.col;
2218
+ levelI = seg.level;
2219
+ forward = seg.forward || 0;
2220
+ leftmost = axisWidth + colContentPositions.left(colI*dis + dit);
2221
+ availWidth = axisWidth + colContentPositions.right(colI*dis + dit) - leftmost;
2222
+ availWidth = Math.min(availWidth-6, availWidth*.95); // TODO: move this to CSS
2223
+ if (levelI) {
2224
+ // indented and thin
2225
+ outerWidth = availWidth / (levelI + forward + 1);
2226
+ }else{
2227
+ if (forward) {
2228
+ // moderately wide, aligned left still
2229
+ outerWidth = ((availWidth / (forward + 1)) - (12/2)) * 2; // 12 is the predicted width of resizer =
2230
+ }else{
2231
+ // can be entire width, aligned left
2232
+ outerWidth = availWidth;
2233
+ }
2234
+ }
2235
+ left = leftmost + // leftmost possible
2236
+ (availWidth / (levelI + forward + 1) * levelI) // indentation
2237
+ * dis + (rtl ? availWidth - outerWidth : 0); // rtl
2238
+ seg.top = top;
2239
+ seg.left = left;
2240
+ seg.outerWidth = outerWidth;
2241
+ seg.outerHeight = bottom - top;
2242
+ html += slotSegHtml(event, seg, className);
2243
+ }
2244
+ slotSegmentContainer[0].innerHTML = html; // faster than html()
2245
+ eventElements = slotSegmentContainer.children();
2246
+
2247
+ // retrieve elements, run through eventRender callback, bind event handlers
2248
+ for (i=0; i<segCnt; i++) {
2249
+ seg = segs[i];
2250
+ event = seg.event;
2251
+ eventElement = $(eventElements[i]); // faster than eq()
2252
+ triggerRes = view.trigger('eventRender', event, event, eventElement);
2253
+ if (triggerRes === false) {
2254
+ eventElement.remove();
2255
+ }else{
2256
+ if (triggerRes && triggerRes !== true) {
2257
+ eventElement.remove();
2258
+ eventElement = $(triggerRes)
2259
+ .css({
2260
+ position: 'absolute',
2261
+ top: seg.top,
2262
+ left: seg.left
2263
+ })
2264
+ .appendTo(slotSegmentContainer);
2265
+ }
2266
+ seg.element = eventElement;
2267
+ if (event._id === modifiedEventId) {
2268
+ slotSegBind(event, eventElement, seg);
2269
+ }else{
2270
+ eventElement[0]._fci = i; // for lazySegBind
2271
+ }
2272
+ view.reportEventElement(event, eventElement);
2273
+ }
2274
+ }
2275
+
2276
+ lazySegBind(slotSegmentContainer, segs, slotSegBind);
2277
+
2278
+ // record event sides and title positions
2279
+ for (i=0; i<segCnt; i++) {
2280
+ seg = segs[i];
2281
+ if (eventElement = seg.element) {
2282
+ val = vsideCache[key = seg.key = cssKey(eventElement[0])];
2283
+ seg.vsides = val === undefined ? (vsideCache[key] = vsides(eventElement[0], true)) : val;
2284
+ val = hsideCache[key];
2285
+ seg.hsides = val === undefined ? (hsideCache[key] = hsides(eventElement[0], true)) : val;
2286
+ titleSpan = eventElement.find('span.fc-event-title');
2287
+ if (titleSpan.length) {
2288
+ seg.titleTop = titleSpan[0].offsetTop;
2289
+ }
2290
+ }
2291
+ }
2292
+
2293
+ // set all positions/dimensions at once
2294
+ for (i=0; i<segCnt; i++) {
2295
+ seg = segs[i];
2296
+ if (eventElement = seg.element) {
2297
+ eventElement[0].style.width = seg.outerWidth - seg.hsides + 'px';
2298
+ eventElement[0].style.height = (height = seg.outerHeight - seg.vsides) + 'px';
2299
+ event = seg.event;
2300
+ if (seg.titleTop !== undefined && height - seg.titleTop < 10) {
2301
+ // not enough room for title, put it in the time header
2302
+ eventElement.find('span.fc-event-time')
2303
+ .text(formatDate(event.start, view.option('timeFormat')) + ' - ' + event.title);
2304
+ eventElement.find('span.fc-event-title')
2305
+ .remove();
2306
+ }
2307
+ view.trigger('eventAfterRender', event, event, eventElement);
2308
+ }
2309
+ }
2310
+
2311
+ }
2312
+
2313
+ function slotSegHtml(event, seg, className) {
2314
+ return "<div class='" + className + event.className.join(' ') + "' style='position:absolute;z-index:8;top:" + seg.top + "px;left:" + seg.left + "px'>" +
2315
+ "<a" + (event.url ? " href='" + htmlEscape(event.url) + "'" : '') + ">" +
2316
+ "<span class='fc-event-bg'></span>" +
2317
+ "<span class='fc-event-time'>" + htmlEscape(formatDates(event.start, event.end, view.option('timeFormat'))) + "</span>" +
2318
+ "<span class='fc-event-title'>" + htmlEscape(event.title) + "</span>" +
2319
+ "</a>" +
2320
+ ((event.editable || event.editable === undefined && options.editable) && !options.disableResizing && $.fn.resizable ?
2321
+ "<div class='ui-resizable-handle ui-resizable-s'>=</div>"
2322
+ : '') +
2323
+ "</div>";
2324
+ }
2325
+
2326
+
2327
+
2328
+ function daySegBind(event, eventElement, seg) {
2329
+ view.eventElementHandlers(event, eventElement);
2330
+ if (event.editable || event.editable === undefined && options.editable) {
2331
+ draggableDayEvent(event, eventElement, seg.isStart);
2332
+ if (seg.isEnd) {
2333
+ view.resizableDayEvent(event, eventElement, colWidth);
2334
+ }
2335
+ }
2336
+ }
2337
+
2338
+
2339
+
2340
+ function slotSegBind(event, eventElement, seg) {
2341
+ view.eventElementHandlers(event, eventElement);
2342
+ if (event.editable || event.editable === undefined && options.editable) {
2343
+ var timeElement = eventElement.find('span.fc-event-time');
2344
+ draggableSlotEvent(event, eventElement, timeElement);
2345
+ if (seg.isEnd) {
2346
+ resizableSlotEvent(event, eventElement, timeElement);
2347
+ }
2348
+ }
2349
+ }
2350
+
2351
+
2352
+
2353
+
2354
+ /* Event Dragging
2355
+ -----------------------------------------------------------------------------*/
2356
+
2357
+
2358
+
2359
+ // when event starts out FULL-DAY
2360
+
2361
+ function draggableDayEvent(event, eventElement, isStart) {
2362
+ if (!options.disableDragging && eventElement.draggable) {
2363
+ var origPosition, origWidth,
2364
+ resetElement,
2365
+ allDay=true,
2366
+ matrix;
2367
+ eventElement.draggable({
2368
+ zIndex: 9,
2369
+ opacity: view.option('dragOpacity', 'month'), // use whatever the month view was using
2370
+ revertDuration: options.dragRevertDuration,
2371
+ start: function(ev, ui) {
2372
+ view.hideEvents(event, eventElement);
2373
+ view.trigger('eventDragStart', eventElement, event, ev, ui);
2374
+ origPosition = eventElement.position();
2375
+ origWidth = eventElement.width();
2376
+ resetElement = function() {
2377
+ if (!allDay) {
2378
+ eventElement
2379
+ .width(origWidth)
2380
+ .height('')
2381
+ .draggable('option', 'grid', null);
2382
+ allDay = true;
2383
+ }
2384
+ };
2385
+ matrix = buildDayMatrix(function(cell) {
2386
+ eventElement.draggable('option', 'revert', !cell || !cell.rowDelta && !cell.colDelta);
2387
+ view.clearOverlays();
2388
+ if (cell) {
2389
+ if (!cell.row) {
2390
+ // on full-days
2391
+ renderDayOverlay(
2392
+ matrix,
2393
+ addDays(cloneDate(event.start), cell.colDelta),
2394
+ addDays(exclEndDay(event), cell.colDelta)
2395
+ );
2396
+ resetElement();
2397
+ }else{
2398
+ // mouse is over bottom slots
2399
+ if (isStart && allDay) {
2400
+ // convert event to temporary slot-event
2401
+ setOuterHeight(
2402
+ eventElement.width(colWidth - 10), // don't use entire width
2403
+ slotHeight * Math.round(
2404
+ (event.end ? ((event.end - event.start)/MINUTE_MS) : options.defaultEventMinutes)
2405
+ /options.slotMinutes)
2406
+ );
2407
+ eventElement.draggable('option', 'grid', [colWidth, 1]);
2408
+ allDay = false;
2409
+ }
2410
+ }
2411
+ }
2412
+ },true);
2413
+ matrix.mouse(ev);
2414
+ },
2415
+ drag: function(ev, ui) {
2416
+ matrix.mouse(ev);
2417
+ },
2418
+ stop: function(ev, ui) {
2419
+ view.trigger('eventDragStop', eventElement, event, ev, ui);
2420
+ view.clearOverlays();
2421
+ var cell = matrix.cell;
2422
+ var dayDelta = dis * (
2423
+ allDay ? // can't trust cell.colDelta when using slot grid
2424
+ (cell ? cell.colDelta : 0) :
2425
+ Math.floor((ui.position.left - origPosition.left) / colWidth)
2426
+ );
2427
+ if (!cell || !dayDelta && !cell.rowDelta) {
2428
+ // over nothing (has reverted)
2429
+ resetElement();
2430
+ if ($.browser.msie) {
2431
+ eventElement.css('filter', ''); // clear IE opacity side-effects
2432
+ }
2433
+ view.showEvents(event, eventElement);
2434
+ }else{
2435
+ eventElement.find('a').removeAttr('href'); // prevents safari from visiting the link
2436
+ view.eventDrop(
2437
+ this, event, dayDelta,
2438
+ allDay ? 0 : // minute delta
2439
+ Math.round((eventElement.offset().top - bodyContent.offset().top) / slotHeight)
2440
+ * options.slotMinutes
2441
+ + minMinute
2442
+ - (event.start.getHours() * 60 + event.start.getMinutes()),
2443
+ allDay, ev, ui
2444
+ );
2445
+ }
2446
+ }
2447
+ });
2448
+ }
2449
+ }
2450
+
2451
+
2452
+
2453
+ // when event starts out IN TIMESLOTS
2454
+
2455
+ function draggableSlotEvent(event, eventElement, timeElement) {
2456
+ if (!options.disableDragging && eventElement.draggable) {
2457
+ var origPosition,
2458
+ resetElement,
2459
+ prevSlotDelta, slotDelta,
2460
+ allDay=false,
2461
+ matrix;
2462
+ eventElement.draggable({
2463
+ zIndex: 9,
2464
+ scroll: false,
2465
+ grid: [colWidth, slotHeight],
2466
+ axis: colCnt==1 ? 'y' : false,
2467
+ opacity: view.option('dragOpacity'),
2468
+ revertDuration: options.dragRevertDuration,
2469
+ start: function(ev, ui) {
2470
+ view.hideEvents(event, eventElement);
2471
+ view.trigger('eventDragStart', eventElement, event, ev, ui);
2472
+ if ($.browser.msie) {
2473
+ eventElement.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
2474
+ }
2475
+ origPosition = eventElement.position();
2476
+ resetElement = function() {
2477
+ // convert back to original slot-event
2478
+ if (allDay) {
2479
+ timeElement.css('display', ''); // show() was causing display=inline
2480
+ eventElement.draggable('option', 'grid', [colWidth, slotHeight]);
2481
+ allDay = false;
2482
+ }
2483
+ };
2484
+ prevSlotDelta = 0;
2485
+ matrix = buildDayMatrix(function(cell) {
2486
+ eventElement.draggable('option', 'revert', !cell);
2487
+ view.clearOverlays();
2488
+ if (cell) {
2489
+ if (!cell.row && options.allDaySlot) { // over full days
2490
+ if (!allDay) {
2491
+ // convert to temporary all-day event
2492
+ allDay = true;
2493
+ timeElement.hide();
2494
+ eventElement.draggable('option', 'grid', null);
2495
+ }
2496
+ renderDayOverlay(
2497
+ matrix,
2498
+ addDays(cloneDate(event.start), cell.colDelta),
2499
+ addDays(exclEndDay(event), cell.colDelta)
2500
+ );
2501
+ }else{ // on slots
2502
+ resetElement();
2503
+ }
2504
+ }
2505
+ },true);
2506
+ matrix.mouse(ev);
2507
+ },
2508
+ drag: function(ev, ui) {
2509
+ slotDelta = Math.round((ui.position.top - origPosition.top) / slotHeight);
2510
+ if (slotDelta != prevSlotDelta) {
2511
+ if (!allDay) {
2512
+ // update time header
2513
+ var minuteDelta = slotDelta*options.slotMinutes,
2514
+ newStart = addMinutes(cloneDate(event.start), minuteDelta),
2515
+ newEnd;
2516
+ if (event.end) {
2517
+ newEnd = addMinutes(cloneDate(event.end), minuteDelta);
2518
+ }
2519
+ timeElement.text(formatDates(newStart, newEnd, view.option('timeFormat')));
2520
+ }
2521
+ prevSlotDelta = slotDelta;
2522
+ }
2523
+ matrix.mouse(ev);
2524
+ },
2525
+ stop: function(ev, ui) {
2526
+ view.clearOverlays();
2527
+ view.trigger('eventDragStop', eventElement, event, ev, ui);
2528
+ var cell = matrix.cell,
2529
+ dayDelta = dis * (
2530
+ allDay ? // can't trust cell.colDelta when using slot grid
2531
+ (cell ? cell.colDelta : 0) :
2532
+ Math.floor((ui.position.left - origPosition.left) / colWidth)
2533
+ );
2534
+ if (!cell || !slotDelta && !dayDelta) {
2535
+ resetElement();
2536
+ if ($.browser.msie) {
2537
+ eventElement
2538
+ .css('filter', '') // clear IE opacity side-effects
2539
+ .find('span.fc-event-bg').css('display', ''); // .show() made display=inline
2540
+ }
2541
+ eventElement.css(origPosition); // sometimes fast drags make event revert to wrong position
2542
+ view.showEvents(event, eventElement);
2543
+ }else{
2544
+ view.eventDrop(
2545
+ this, event, dayDelta,
2546
+ allDay ? 0 : slotDelta * options.slotMinutes, // minute delta
2547
+ allDay, ev, ui
2548
+ );
2549
+ }
2550
+ }
2551
+ });
2552
+ }
2553
+ }
2554
+
2555
+
2556
+
2557
+
2558
+ /* Event Resizing
2559
+ -----------------------------------------------------------------------------*/
2560
+
2561
+ // for TIMESLOT events
2562
+
2563
+ function resizableSlotEvent(event, eventElement, timeElement) {
2564
+ if (!options.disableResizing && eventElement.resizable) {
2565
+ var slotDelta, prevSlotDelta;
2566
+ eventElement.resizable({
2567
+ handles: {
2568
+ s: 'div.ui-resizable-s'
2569
+ },
2570
+ grid: slotHeight,
2571
+ start: function(ev, ui) {
2572
+ slotDelta = prevSlotDelta = 0;
2573
+ view.hideEvents(event, eventElement);
2574
+ if ($.browser.msie && $.browser.version == '6.0') {
2575
+ eventElement.css('overflow', 'hidden');
2576
+ }
2577
+ eventElement.css('z-index', 9);
2578
+ view.trigger('eventResizeStart', this, event, ev, ui);
2579
+ },
2580
+ resize: function(ev, ui) {
2581
+ // don't rely on ui.size.height, doesn't take grid into account
2582
+ slotDelta = Math.round((Math.max(slotHeight, eventElement.height()) - ui.originalSize.height) / slotHeight);
2583
+ if (slotDelta != prevSlotDelta) {
2584
+ timeElement.text(
2585
+ formatDates(
2586
+ event.start,
2587
+ (!slotDelta && !event.end) ? null : // no change, so don't display time range
2588
+ addMinutes(view.eventEnd(event), options.slotMinutes*slotDelta),
2589
+ view.option('timeFormat')
2590
+ )
2591
+ );
2592
+ prevSlotDelta = slotDelta;
2593
+ }
2594
+ },
2595
+ stop: function(ev, ui) {
2596
+ view.trigger('eventResizeStop', this, event, ev, ui);
2597
+ if (slotDelta) {
2598
+ view.eventResize(this, event, 0, options.slotMinutes*slotDelta, ev, ui);
2599
+ }else{
2600
+ eventElement.css('z-index', 8);
2601
+ view.showEvents(event, eventElement);
2602
+ // BUG: if event was really short, need to put title back in span
2603
+ }
2604
+ }
2605
+ });
2606
+ }
2607
+ }
2608
+
2609
+
2610
+
2611
+
2612
+ /* Selecting
2613
+ -----------------------------------------------------------------------------*/
2614
+
2615
+ daySelectionManager = new SelectionManager(
2616
+ view,
2617
+ unselect,
2618
+ function(startDate, endDate, allDay) {
2619
+ renderDayOverlay(
2620
+ selectionMatrix,
2621
+ startDate,
2622
+ addDays(cloneDate(endDate), 1)
2623
+ );
2624
+ },
2625
+ clearSelection
2626
+ );
2627
+
2628
+ function daySelectionMousedown(ev) {
2629
+ if (view.option('selectable')) {
2630
+ selectionMatrix = buildDayMatrix(function(cell) {
2631
+ if (cell) {
2632
+ var d = dayColDate(cell.col);
2633
+ daySelectionManager.drag(d, d, true);
2634
+ }else{
2635
+ daySelectionManager.drag();
2636
+ }
2637
+ });
2638
+ documentDragHelp(
2639
+ function(ev) {
2640
+ selectionMatrix.mouse(ev);
2641
+ },
2642
+ function(ev) {
2643
+ daySelectionManager.dragStop(ev);
2644
+ }
2645
+ );
2646
+ daySelectionManager.dragStart(ev);
2647
+ selectionMatrix.mouse(ev);
2648
+ return false; // prevent auto-unselect and text selection
2649
+ }
2650
+ }
2651
+
2652
+ slotSelectionManager = new SelectionManager(
2653
+ view,
2654
+ unselect,
2655
+ renderSlotSelection,
2656
+ clearSelection
2657
+ );
2658
+
2659
+ function slotSelectionMousedown(ev) {
2660
+ if (view.option('selectable')) {
2661
+ selectionMatrix = buildSlotMatrix(function(cell) {
2662
+ if (cell) {
2663
+ var d = slotCellDate(cell.row, cell.origCol);
2664
+ slotSelectionManager.drag(d, addMinutes(cloneDate(d), options.slotMinutes), false);
2665
+ }else{
2666
+ slotSelectionManager.drag();
2667
+ }
2668
+ });
2669
+ documentDragHelp(
2670
+ function(ev) {
2671
+ selectionMatrix.mouse(ev);
2672
+ },
2673
+ function(ev) {
2674
+ slotSelectionManager.dragStop(ev);
2675
+ }
2676
+ );
2677
+ slotSelectionManager.dragStart(ev);
2678
+ selectionMatrix.mouse(ev);
2679
+ return false; // prevent auto-unselect and text selection
2680
+ }
2681
+ }
2682
+
2683
+ documentUnselectAuto(view, unselect);
2684
+
2685
+ this.select = function(start, end, allDay) {
2686
+ if (allDay) {
2687
+ if (options.allDaySlot) {
2688
+ if (!end) {
2689
+ end = cloneDate(start);
2690
+ }
2691
+ selectionMatrix = buildDayMatrix();
2692
+ daySelectionManager.select(start, end, allDay);
2693
+ }
2694
+ }else{
2695
+ if (!end) {
2696
+ end = addMinutes(cloneDate(start), options.slotMinutes);
2697
+ }
2698
+ selectionMatrix = buildSlotMatrix();
2699
+ slotSelectionManager.select(start, end, allDay);
2700
+ }
2701
+ };
2702
+
2703
+ function unselect() {
2704
+ slotSelectionManager.unselect();
2705
+ daySelectionManager.unselect();
2706
+ }
2707
+ this.unselect = unselect;
2708
+
2709
+
2710
+
2711
+ /* Selecting drawing utils
2712
+ -----------------------------------------------------------------------------*/
2713
+
2714
+ function renderSlotSelection(startDate, endDate) {
2715
+ var helperOption = view.option('selectHelper');
2716
+ if (helperOption) {
2717
+ var col = dayDiff(startDate, view.visStart);
2718
+ if (col >= 0 && col < colCnt) { // only works when times are on same day
2719
+ var rect = selectionMatrix.rect(0, col*dis+dit, 1, col*dis+dit+1, bodyContent); // only for horizontal coords
2720
+ var top = timePosition(startDate, startDate);
2721
+ var bottom = timePosition(startDate, endDate);
2722
+ if (bottom > top) { // protect against selections that are entirely before or after visible range
2723
+ rect.top = top;
2724
+ rect.height = bottom - top;
2725
+ rect.left += 2;
2726
+ rect.width -= 5;
2727
+ if ($.isFunction(helperOption)) {
2728
+ var helperRes = helperOption(startDate, endDate);
2729
+ if (helperRes) {
2730
+ rect.position = 'absolute';
2731
+ rect.zIndex = 8;
2732
+ selectionHelper = $(helperRes)
2733
+ .css(rect)
2734
+ .appendTo(bodyContent);
2735
+ }
2736
+ }else{
2737
+ selectionHelper = $(slotSegHtml(
2738
+ {
2739
+ title: '',
2740
+ start: startDate,
2741
+ end: endDate,
2742
+ className: [],
2743
+ editable: false
2744
+ },
2745
+ rect,
2746
+ 'fc-event fc-event-vert fc-corner-top fc-corner-bottom '
2747
+ ));
2748
+ if ($.browser.msie) {
2749
+ selectionHelper.find('span.fc-event-bg').hide(); // nested opacities mess up in IE, just hide
2750
+ }
2751
+ selectionHelper.css('opacity', view.option('dragOpacity'));
2752
+ }
2753
+ if (selectionHelper) {
2754
+ slotBind(selectionHelper);
2755
+ bodyContent.append(selectionHelper);
2756
+ setOuterWidth(selectionHelper, rect.width, true); // needs to be after appended
2757
+ setOuterHeight(selectionHelper, rect.height, true);
2758
+ }
2759
+ }
2760
+ }
2761
+ }else{
2762
+ renderSlotOverlay(selectionMatrix, startDate, endDate);
2763
+ }
2764
+ }
2765
+
2766
+ function clearSelection() {
2767
+ clearOverlays();
2768
+ if (selectionHelper) {
2769
+ selectionHelper.remove();
2770
+ selectionHelper = null;
2771
+ }
2772
+ }
2773
+
2774
+
2775
+
2776
+
2777
+
2778
+ /* Semi-transparent Overlay Helpers
2779
+ -----------------------------------------------------*/
2780
+
2781
+ function renderDayOverlay(matrix, startDate, endDate) {
2782
+ var startCol, endCol;
2783
+ if (rtl) {
2784
+ startCol = dayDiff(endDate, view.visStart)*dis+dit+1;
2785
+ endCol = dayDiff(startDate, view.visStart)*dis+dit+1;
2786
+ }else{
2787
+ startCol = dayDiff(startDate, view.visStart);
2788
+ endCol = dayDiff(endDate, view.visStart);
2789
+ }
2790
+ startCol = Math.max(0, startCol);
2791
+ endCol = Math.min(colCnt, endCol);
2792
+ if (startCol < endCol) {
2793
+ var rect = matrix.rect(0, startCol, 1, endCol, head);
2794
+ dayBind(
2795
+ view.renderOverlay(rect, head)
2796
+ );
2797
+ }
2798
+ }
2799
+
2800
+ function renderSlotOverlay(matrix, overlayStart, overlayEnd) {
2801
+ var dayStart = cloneDate(view.visStart);
2802
+ var dayEnd = addDays(cloneDate(dayStart), 1);
2803
+ for (var i=0; i<colCnt; i++) {
2804
+ var stretchStart = new Date(Math.max(dayStart, overlayStart));
2805
+ var stretchEnd = new Date(Math.min(dayEnd, overlayEnd));
2806
+ if (stretchStart < stretchEnd) {
2807
+ var rect = matrix.rect(0, i*dis+dit, 1, i*dis+dit+1, bodyContent); // only use it for horizontal coords
2808
+ var top = timePosition(dayStart, stretchStart);
2809
+ var bottom = timePosition(dayStart, stretchEnd);
2810
+ rect.top = top;
2811
+ rect.height = bottom - top;
2812
+ slotBind(
2813
+ view.renderOverlay(rect, bodyContent)
2814
+ );
2815
+ }
2816
+ addDays(dayStart, 1);
2817
+ addDays(dayEnd, 1);
2818
+ }
2819
+ }
2820
+
2821
+ function clearOverlays() {
2822
+ view.clearOverlays();
2823
+ }
2824
+
2825
+
2826
+
2827
+
2828
+ /* Coordinate Utilities
2829
+ -----------------------------------------------------------------------------*/
2830
+
2831
+ // get the Y coordinate of the given time on the given day (both Date objects)
2832
+ function timePosition(day, time) { // both date objects. day holds 00:00 of current day
2833
+ day = cloneDate(day, true);
2834
+ if (time < addMinutes(cloneDate(day), minMinute)) {
2835
+ return 0;
2836
+ }
2837
+ if (time >= addMinutes(cloneDate(day), maxMinute)) {
2838
+ return bodyContent.height();
2839
+ }
2840
+ var slotMinutes = options.slotMinutes,
2841
+ minutes = time.getHours()*60 + time.getMinutes() - minMinute,
2842
+ slotI = Math.floor(minutes / slotMinutes),
2843
+ slotTop = slotTopCache[slotI];
2844
+ if (slotTop === undefined) {
2845
+ slotTop = slotTopCache[slotI] = body.find('tr:eq(' + slotI + ') td div')[0].offsetTop;
2846
+ }
2847
+ return Math.max(0, Math.round(
2848
+ slotTop - 1 + slotHeight * ((minutes % slotMinutes) / slotMinutes)
2849
+ ));
2850
+ }
2851
+
2852
+ function buildDayMatrix(changeCallback, includeSlotArea) {
2853
+ var rowElements = options.allDaySlot ? head.find('td') : $([]);
2854
+ if (includeSlotArea) {
2855
+ rowElements = rowElements.add(body);
2856
+ }
2857
+ return new HoverMatrix(rowElements, bg.find('td'), changeCallback);
2858
+ }
2859
+
2860
+ function buildSlotMatrix(changeCallback) {
2861
+ return new HoverMatrix(bodyTable.find('td'), bg.find('td'), changeCallback);
2862
+ }
2863
+
2864
+
2865
+
2866
+
2867
+ /* Date Utilities
2868
+ ----------------------------------------------------*/
2869
+
2870
+ function slotEventEnd(event) {
2871
+ if (event.end) {
2872
+ return cloneDate(event.end);
2873
+ }else{
2874
+ return addMinutes(cloneDate(event.start), options.defaultEventMinutes);
2875
+ }
2876
+ }
2877
+
2878
+ function dayOfWeekCol(dayOfWeek) {
2879
+ return ((dayOfWeek - Math.max(firstDay,nwe)+colCnt) % colCnt)*dis+dit;
2880
+ }
2881
+
2882
+
2883
+ // generating dates from cell row & columns
2884
+
2885
+ function dayColDate(col) {
2886
+ return addDays(cloneDate(view.visStart), col*dis+dit);
2887
+ }
2888
+
2889
+ function slotCellDate(row, col) {
2890
+ var d = dayColDate(col);
2891
+ addMinutes(d, minMinute + row*options.slotMinutes);
2892
+ return d;
2893
+ }
2894
+
2895
+
2896
+
2897
+ }
2898
+
2899
+
2900
+ // count the number of colliding, higher-level segments (for event squishing)
2901
+
2902
+ function countForwardSegs(levels) {
2903
+ var i, j, k, level, segForward, segBack;
2904
+ for (i=levels.length-1; i>0; i--) {
2905
+ level = levels[i];
2906
+ for (j=0; j<level.length; j++) {
2907
+ segForward = level[j];
2908
+ for (k=0; k<levels[i-1].length; k++) {
2909
+ segBack = levels[i-1][k];
2910
+ if (segsCollide(segForward, segBack)) {
2911
+ segBack.forward = Math.max(segBack.forward||0, (segForward.forward||0)+1);
2912
+ }
2913
+ }
2914
+ }
2915
+ }
2916
+ }
2917
+
2918
+
2919
+ /* Methods & Utilities for All Views
2920
+ -----------------------------------------------------------------------------*/
2921
+
2922
+ var viewMethods = {
2923
+
2924
+ /*
2925
+ * Objects inheriting these methods must implement the following properties/methods:
2926
+ * - title
2927
+ * - start
2928
+ * - end
2929
+ * - visStart
2930
+ * - visEnd
2931
+ * - defaultEventEnd(event)
2932
+ * - render(events)
2933
+ * - rerenderEvents()
2934
+ *
2935
+ *
2936
+ * z-index reservations:
2937
+ * 3 - day-overlay
2938
+ * 8 - events
2939
+ * 9 - dragging/resizing events
2940
+ *
2941
+ */
2942
+
2943
+
2944
+
2945
+ init: function(element, options) {
2946
+ this.element = element;
2947
+ this.options = options;
2948
+ this.eventsByID = {};
2949
+ this.eventElements = [];
2950
+ this.eventElementsByID = {};
2951
+ this.usedOverlays = [];
2952
+ this.unusedOverlays = [];
2953
+ },
2954
+
2955
+
2956
+
2957
+ // triggers an event handler, always append view as last arg
2958
+
2959
+ trigger: function(name, thisObj) {
2960
+ if (this.options[name]) {
2961
+ return this.options[name].apply(thisObj || this, Array.prototype.slice.call(arguments, 2).concat([this]));
2962
+ }
2963
+ },
2964
+
2965
+
2966
+
2967
+ // returns a Date object for an event's end
2968
+
2969
+ eventEnd: function(event) {
2970
+ return event.end ? cloneDate(event.end) : this.defaultEventEnd(event); // TODO: make sure always using copies
2971
+ },
2972
+
2973
+
2974
+
2975
+ // report when view receives new events
2976
+
2977
+ reportEvents: function(events) { // events are already normalized at this point
2978
+ var i, len=events.length, event,
2979
+ eventsByID = this.eventsByID = {};
2980
+ for (i=0; i<len; i++) {
2981
+ event = events[i];
2982
+ if (eventsByID[event._id]) {
2983
+ eventsByID[event._id].push(event);
2984
+ }else{
2985
+ eventsByID[event._id] = [event];
2986
+ }
2987
+ }
2988
+ },
2989
+
2990
+
2991
+
2992
+ // report when view creates an element for an event
2993
+
2994
+ reportEventElement: function(event, element) {
2995
+ this.eventElements.push(element);
2996
+ var eventElementsByID = this.eventElementsByID;
2997
+ if (eventElementsByID[event._id]) {
2998
+ eventElementsByID[event._id].push(element);
2999
+ }else{
3000
+ eventElementsByID[event._id] = [element];
3001
+ }
3002
+ },
3003
+
3004
+
3005
+
3006
+ // event element manipulation
3007
+
3008
+ _clearEvents: function() { // only resets hashes
3009
+ this.eventElements = [];
3010
+ this.eventElementsByID = {};
3011
+ },
3012
+
3013
+ showEvents: function(event, exceptElement) {
3014
+ this._eee(event, exceptElement, 'show');
3015
+ },
3016
+
3017
+ hideEvents: function(event, exceptElement) {
3018
+ this._eee(event, exceptElement, 'hide');
3019
+ },
3020
+
3021
+ _eee: function(event, exceptElement, funcName) { // event-element-each
3022
+ var elements = this.eventElementsByID[event._id],
3023
+ i, len = elements.length;
3024
+ for (i=0; i<len; i++) {
3025
+ if (elements[i][0] != exceptElement[0]) { // AHAHAHAHAHAHAHAH
3026
+ elements[i][funcName]();
3027
+ }
3028
+ }
3029
+ },
3030
+
3031
+
3032
+
3033
+ // event modification reporting
3034
+
3035
+ eventDrop: function(e, event, dayDelta, minuteDelta, allDay, ev, ui) {
3036
+ var view = this,
3037
+ oldAllDay = event.allDay,
3038
+ eventId = event._id;
3039
+ view.moveEvents(view.eventsByID[eventId], dayDelta, minuteDelta, allDay);
3040
+ view.trigger('eventDrop', e, event, dayDelta, minuteDelta, allDay, function() { // TODO: change docs
3041
+ // TODO: investigate cases where this inverse technique might not work
3042
+ view.moveEvents(view.eventsByID[eventId], -dayDelta, -minuteDelta, oldAllDay);
3043
+ view.rerenderEvents();
3044
+ }, ev, ui);
3045
+ view.eventsChanged = true;
3046
+ view.rerenderEvents(eventId);
3047
+ },
3048
+
3049
+ eventResize: function(e, event, dayDelta, minuteDelta, ev, ui) {
3050
+ var view = this,
3051
+ eventId = event._id;
3052
+ view.elongateEvents(view.eventsByID[eventId], dayDelta, minuteDelta);
3053
+ view.trigger('eventResize', e, event, dayDelta, minuteDelta, function() {
3054
+ // TODO: investigate cases where this inverse technique might not work
3055
+ view.elongateEvents(view.eventsByID[eventId], -dayDelta, -minuteDelta);
3056
+ view.rerenderEvents();
3057
+ }, ev, ui);
3058
+ view.eventsChanged = true;
3059
+ view.rerenderEvents(eventId);
3060
+ },
3061
+
3062
+
3063
+
3064
+ // event modification
3065
+
3066
+ moveEvents: function(events, dayDelta, minuteDelta, allDay) {
3067
+ minuteDelta = minuteDelta || 0;
3068
+ for (var e, len=events.length, i=0; i<len; i++) {
3069
+ e = events[i];
3070
+ if (allDay !== undefined) {
3071
+ e.allDay = allDay;
3072
+ }
3073
+ addMinutes(addDays(e.start, dayDelta, true), minuteDelta);
3074
+ if (e.end) {
3075
+ e.end = addMinutes(addDays(e.end, dayDelta, true), minuteDelta);
3076
+ }
3077
+ normalizeEvent(e, this.options);
3078
+ }
3079
+ },
3080
+
3081
+ elongateEvents: function(events, dayDelta, minuteDelta) {
3082
+ minuteDelta = minuteDelta || 0;
3083
+ for (var e, len=events.length, i=0; i<len; i++) {
3084
+ e = events[i];
3085
+ e.end = addMinutes(addDays(this.eventEnd(e), dayDelta, true), minuteDelta);
3086
+ normalizeEvent(e, this.options);
3087
+ }
3088
+ },
3089
+
3090
+
3091
+
3092
+ // semi-transparent overlay (while dragging or selecting)
3093
+
3094
+ renderOverlay: function(rect, parent) {
3095
+ var e = this.unusedOverlays.shift();
3096
+ if (!e) {
3097
+ e = $("<div class='fc-cell-overlay' style='position:absolute;z-index:3'/>");
3098
+ }
3099
+ if (e[0].parentNode != parent[0]) {
3100
+ e.appendTo(parent);
3101
+ }
3102
+ this.usedOverlays.push(e.css(rect).show());
3103
+ return e;
3104
+ },
3105
+
3106
+ clearOverlays: function() {
3107
+ var e;
3108
+ while (e = this.usedOverlays.shift()) {
3109
+ this.unusedOverlays.push(e.hide().unbind());
3110
+ }
3111
+ },
3112
+
3113
+
3114
+
3115
+
3116
+ // common horizontal event resizing
3117
+
3118
+ resizableDayEvent: function(event, eventElement, colWidth) {
3119
+ var view = this;
3120
+ if (!view.options.disableResizing && eventElement.resizable) {
3121
+ eventElement.resizable({
3122
+ handles: view.options.isRTL ? {w:'div.ui-resizable-w'} : {e:'div.ui-resizable-e'},
3123
+ grid: colWidth,
3124
+ minWidth: colWidth/2, // need this or else IE throws errors when too small
3125
+ containment: view.element.parent().parent(), // the main element...
3126
+ // ... a fix. wouldn't allow extending to last column in agenda views (jq ui bug?)
3127
+ start: function(ev, ui) {
3128
+ eventElement.css('z-index', 9);
3129
+ view.hideEvents(event, eventElement);
3130
+ view.trigger('eventResizeStart', this, event, ev, ui);
3131
+ },
3132
+ stop: function(ev, ui) {
3133
+ view.trigger('eventResizeStop', this, event, ev, ui);
3134
+ // ui.size.width wasn't working with grid correctly, use .width()
3135
+ var dayDelta = Math.round((eventElement.width() - ui.originalSize.width) / colWidth);
3136
+ if (dayDelta) {
3137
+ view.eventResize(this, event, dayDelta, 0, ev, ui);
3138
+ }else{
3139
+ eventElement.css('z-index', 8);
3140
+ view.showEvents(event, eventElement);
3141
+ }
3142
+ }
3143
+ });
3144
+ }
3145
+ },
3146
+
3147
+
3148
+
3149
+ // attaches eventClick, eventMouseover, eventMouseout
3150
+
3151
+ eventElementHandlers: function(event, eventElement) {
3152
+ var view = this;
3153
+ eventElement
3154
+ .click(function(ev) {
3155
+ if (!eventElement.hasClass('ui-draggable-dragging') &&
3156
+ !eventElement.hasClass('ui-resizable-resizing')) {
3157
+ return view.trigger('eventClick', this, event, ev);
3158
+ }
3159
+ })
3160
+ .hover(
3161
+ function(ev) {
3162
+ view.trigger('eventMouseover', this, event, ev);
3163
+ },
3164
+ function(ev) {
3165
+ view.trigger('eventMouseout', this, event, ev);
3166
+ }
3167
+ );
3168
+ },
3169
+
3170
+
3171
+
3172
+ // get a property from the 'options' object, using smart view naming
3173
+
3174
+ option: function(name, viewName) {
3175
+ var v = this.options[name];
3176
+ if (typeof v == 'object') {
3177
+ return smartProperty(v, viewName || this.name);
3178
+ }
3179
+ return v;
3180
+ },
3181
+
3182
+
3183
+
3184
+ // event rendering utilities
3185
+
3186
+ sliceSegs: function(events, visEventEnds, start, end) {
3187
+ var segs = [],
3188
+ i, len=events.length, event,
3189
+ eventStart, eventEnd,
3190
+ segStart, segEnd,
3191
+ isStart, isEnd;
3192
+ for (i=0; i<len; i++) {
3193
+ event = events[i];
3194
+ eventStart = event.start;
3195
+ eventEnd = visEventEnds[i];
3196
+ if (eventEnd > start && eventStart < end) {
3197
+ if (eventStart < start) {
3198
+ segStart = cloneDate(start);
3199
+ isStart = false;
3200
+ }else{
3201
+ segStart = eventStart;
3202
+ isStart = true;
3203
+ }
3204
+ if (eventEnd > end) {
3205
+ segEnd = cloneDate(end);
3206
+ isEnd = false;
3207
+ }else{
3208
+ segEnd = eventEnd;
3209
+ isEnd = true;
3210
+ }
3211
+ segs.push({
3212
+ event: event,
3213
+ start: segStart,
3214
+ end: segEnd,
3215
+ isStart: isStart,
3216
+ isEnd: isEnd,
3217
+ msLength: segEnd - segStart
3218
+ });
3219
+ }
3220
+ }
3221
+ return segs.sort(segCmp);
3222
+ }
3223
+
3224
+
3225
+ };
3226
+
3227
+
3228
+
3229
+ function lazySegBind(container, segs, bindHandlers) {
3230
+ container.unbind('mouseover').mouseover(function(ev) {
3231
+ var parent=ev.target, e,
3232
+ i, seg;
3233
+ while (parent != this) {
3234
+ e = parent;
3235
+ parent = parent.parentNode;
3236
+ }
3237
+ if ((i = e._fci) !== undefined) {
3238
+ e._fci = undefined;
3239
+ seg = segs[i];
3240
+ bindHandlers(seg.event, seg.element, seg);
3241
+ $(ev.target).trigger(ev);
3242
+ }
3243
+ ev.stopPropagation();
3244
+ });
3245
+ }
3246
+
3247
+
3248
+
3249
+ // event rendering calculation utilities
3250
+
3251
+ function stackSegs(segs) {
3252
+ var levels = [],
3253
+ i, len = segs.length, seg,
3254
+ j, collide, k;
3255
+ for (i=0; i<len; i++) {
3256
+ seg = segs[i];
3257
+ j = 0; // the level index where seg should belong
3258
+ while (true) {
3259
+ collide = false;
3260
+ if (levels[j]) {
3261
+ for (k=0; k<levels[j].length; k++) {
3262
+ if (segsCollide(levels[j][k], seg)) {
3263
+ collide = true;
3264
+ break;
3265
+ }
3266
+ }
3267
+ }
3268
+ if (collide) {
3269
+ j++;
3270
+ }else{
3271
+ break;
3272
+ }
3273
+ }
3274
+ if (levels[j]) {
3275
+ levels[j].push(seg);
3276
+ }else{
3277
+ levels[j] = [seg];
3278
+ }
3279
+ }
3280
+ return levels;
3281
+ }
3282
+
3283
+ function segCmp(a, b) {
3284
+ return (b.msLength - a.msLength) * 100 + (a.event.start - b.event.start);
3285
+ }
3286
+
3287
+ function segsCollide(seg1, seg2) {
3288
+ return seg1.end > seg2.start && seg1.start < seg2.end;
3289
+ }
3290
+
3291
+
3292
+
3293
+
3294
+ function SelectionManager(view, initFunc, displayFunc, clearFunc) {
3295
+
3296
+ var t = this;
3297
+ var selected = false;
3298
+ var initialElement;
3299
+ var initialRange;
3300
+ var start;
3301
+ var end;
3302
+ var allDay;
3303
+
3304
+
3305
+ t.dragStart = function(ev) {
3306
+ initFunc();
3307
+ start = end = undefined;
3308
+ initialRange = undefined;
3309
+ initialElement = ev.currentTarget;
3310
+ };
3311
+
3312
+
3313
+ t.drag = function(currentStart, currentEnd, currentAllDay) {
3314
+ if (currentStart) {
3315
+ var range = [currentStart, currentEnd];
3316
+ if (!initialRange) {
3317
+ initialRange = range;
3318
+ }
3319
+ var dates = initialRange.concat(range).sort(cmp);
3320
+ start = dates[0];
3321
+ end = dates[3];
3322
+ allDay = currentAllDay;
3323
+ clearFunc();
3324
+ displayFunc(cloneDate(start), cloneDate(end), allDay);
3325
+ }else{
3326
+ // called with no arguments
3327
+ start = end = undefined;
3328
+ clearFunc();
3329
+ }
3330
+ };
3331
+
3332
+
3333
+ t.dragStop = function(ev) {
3334
+ if (start) {
3335
+ if (+initialRange[0] == +start && +initialRange[1] == +end) {
3336
+ view.trigger('dayClick', initialElement, start, allDay, ev);
3337
+ }
3338
+ _select();
3339
+ }
3340
+ };
3341
+
3342
+
3343
+ t.select = function(newStart, newEnd, newAllDay) {
3344
+ initFunc();
3345
+ start = newStart;
3346
+ end = newEnd;
3347
+ allDay = newAllDay;
3348
+ displayFunc(cloneDate(start), cloneDate(end), allDay);
3349
+ _select();
3350
+ };
3351
+
3352
+
3353
+ function _select() { // just set the selected flag, and trigger
3354
+ selected = true;
3355
+ view.trigger('select', view, start, end, allDay);
3356
+ }
3357
+
3358
+
3359
+ function unselect() {
3360
+ if (selected) {
3361
+ selected = false;
3362
+ start = end = undefined;
3363
+ clearFunc();
3364
+ view.trigger('unselect', view);
3365
+ }
3366
+ }
3367
+ t.unselect = unselect;
3368
+
3369
+ }
3370
+
3371
+
3372
+ function documentDragHelp(mousemove, mouseup) {
3373
+ function _mouseup(ev) {
3374
+ mouseup(ev);
3375
+ $(document)
3376
+ .unbind('mousemove', mousemove)
3377
+ .unbind('mouseup', _mouseup);
3378
+ }
3379
+ $(document)
3380
+ .mousemove(mousemove)
3381
+ .mouseup(_mouseup);
3382
+ }
3383
+
3384
+
3385
+ function documentUnselectAuto(view, unselectFunc) {
3386
+ if (view.option('selectable') && view.option('unselectAuto')) {
3387
+ $(document).mousedown(function(ev) {
3388
+ var ignore = view.option('unselectCancel');
3389
+ if (ignore) {
3390
+ if ($(ev.target).parents(ignore).length) { // could be optimized to stop after first match
3391
+ return;
3392
+ }
3393
+ }
3394
+ unselectFunc();
3395
+ });
3396
+ }
3397
+ }
3398
+
3399
+
3400
+
3401
+
3402
+ /* Date Math
3403
+ -----------------------------------------------------------------------------*/
3404
+
3405
+ var DAY_MS = 86400000,
3406
+ HOUR_MS = 3600000,
3407
+ MINUTE_MS = 60000;
3408
+
3409
+ function addYears(d, n, keepTime) {
3410
+ d.setFullYear(d.getFullYear() + n);
3411
+ if (!keepTime) {
3412
+ clearTime(d);
3413
+ }
3414
+ return d;
3415
+ }
3416
+
3417
+ function addMonths(d, n, keepTime) { // prevents day overflow/underflow
3418
+ if (+d) { // prevent infinite looping on invalid dates
3419
+ var m = d.getMonth() + n,
3420
+ check = cloneDate(d);
3421
+ check.setDate(1);
3422
+ check.setMonth(m);
3423
+ d.setMonth(m);
3424
+ if (!keepTime) {
3425
+ clearTime(d);
3426
+ }
3427
+ while (d.getMonth() != check.getMonth()) {
3428
+ d.setDate(d.getDate() + (d < check ? 1 : -1));
3429
+ }
3430
+ }
3431
+ return d;
3432
+ }
3433
+
3434
+ function addDays(d, n, keepTime) { // deals with daylight savings
3435
+ if (+d) {
3436
+ var dd = d.getDate() + n,
3437
+ check = cloneDate(d);
3438
+ check.setHours(9); // set to middle of day
3439
+ check.setDate(dd);
3440
+ d.setDate(dd);
3441
+ if (!keepTime) {
3442
+ clearTime(d);
3443
+ }
3444
+ fixDate(d, check);
3445
+ }
3446
+ return d;
3447
+ }
3448
+ fc.addDays = addDays;
3449
+
3450
+ function fixDate(d, check) { // force d to be on check's YMD, for daylight savings purposes
3451
+ if (+d) { // prevent infinite looping on invalid dates
3452
+ while (d.getDate() != check.getDate()) {
3453
+ d.setTime(+d + (d < check ? 1 : -1) * HOUR_MS);
3454
+ }
3455
+ }
3456
+ }
3457
+
3458
+ function addMinutes(d, n) {
3459
+ d.setMinutes(d.getMinutes() + n);
3460
+ return d;
3461
+ }
3462
+
3463
+ function clearTime(d) {
3464
+ d.setHours(0);
3465
+ d.setMinutes(0);
3466
+ d.setSeconds(0);
3467
+ d.setMilliseconds(0);
3468
+ return d;
3469
+ }
3470
+
3471
+ function cloneDate(d, dontKeepTime) {
3472
+ if (dontKeepTime) {
3473
+ return clearTime(new Date(+d));
3474
+ }
3475
+ return new Date(+d);
3476
+ }
3477
+ fc.cloneDate = cloneDate;
3478
+
3479
+ function zeroDate() { // returns a Date with time 00:00:00 and dateOfMonth=1
3480
+ var i=0, d;
3481
+ do {
3482
+ d = new Date(1970, i++, 1);
3483
+ } while (d.getHours()); // != 0
3484
+ return d;
3485
+ }
3486
+
3487
+ function skipWeekend(date, inc, excl) {
3488
+ inc = inc || 1;
3489
+ while (!date.getDay() || (excl && date.getDay()==1 || !excl && date.getDay()==6)) {
3490
+ addDays(date, inc);
3491
+ }
3492
+ return date;
3493
+ }
3494
+
3495
+ function dayDiff(d1, d2) { // d1 - d2
3496
+ return Math.round((cloneDate(d1, true) - cloneDate(d2, true)) / DAY_MS);
3497
+ }
3498
+
3499
+
3500
+
3501
+ /* Date Parsing
3502
+ -----------------------------------------------------------------------------*/
3503
+
3504
+ var parseDate = fc.parseDate = function(s) {
3505
+ if (typeof s == 'object') { // already a Date object
3506
+ return s;
3507
+ }
3508
+ if (typeof s == 'number') { // a UNIX timestamp
3509
+ return new Date(s * 1000);
3510
+ }
3511
+ if (typeof s == 'string') {
3512
+ if (s.match(/^\d+$/)) { // a UNIX timestamp
3513
+ return new Date(parseInt(s) * 1000);
3514
+ }
3515
+ return parseISO8601(s, true) || (s ? new Date(s) : null);
3516
+ }
3517
+ // TODO: never return invalid dates (like from new Date(<string>)), return null instead
3518
+ return null;
3519
+ };
3520
+
3521
+ var parseISO8601 = fc.parseISO8601 = function(s, ignoreTimezone) {
3522
+ // derived from http://delete.me.uk/2005/03/iso8601.html
3523
+ // TODO: for a know glitch/feature, read tests/issue_206_parseDate_dst.html
3524
+ 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})))?)?)?)?$/);
3525
+ if (!m) {
3526
+ return null;
3527
+ }
3528
+ var date = new Date(m[1], 0, 1),
3529
+ check = new Date(m[1], 0, 1, 9, 0),
3530
+ offset = 0;
3531
+ if (m[3]) {
3532
+ date.setMonth(m[3] - 1);
3533
+ check.setMonth(m[3] - 1);
3534
+ }
3535
+ if (m[5]) {
3536
+ date.setDate(m[5]);
3537
+ check.setDate(m[5]);
3538
+ }
3539
+ fixDate(date, check);
3540
+ if (m[7]) {
3541
+ date.setHours(m[7]);
3542
+ }
3543
+ if (m[8]) {
3544
+ date.setMinutes(m[8]);
3545
+ }
3546
+ if (m[10]) {
3547
+ date.setSeconds(m[10]);
3548
+ }
3549
+ if (m[12]) {
3550
+ date.setMilliseconds(Number("0." + m[12]) * 1000);
3551
+ }
3552
+ fixDate(date, check);
3553
+ if (!ignoreTimezone) {
3554
+ if (m[14]) {
3555
+ offset = Number(m[16]) * 60 + Number(m[17]);
3556
+ offset *= m[15] == '-' ? 1 : -1;
3557
+ }
3558
+ offset -= date.getTimezoneOffset();
3559
+ }
3560
+ return new Date(+date + (offset * 60 * 1000));
3561
+ };
3562
+
3563
+ var parseTime = fc.parseTime = function(s) { // returns minutes since start of day
3564
+ if (typeof s == 'number') { // an hour
3565
+ return s * 60;
3566
+ }
3567
+ if (typeof s == 'object') { // a Date object
3568
+ return s.getHours() * 60 + s.getMinutes();
3569
+ }
3570
+ var m = s.match(/(\d+)(?::(\d+))?\s*(\w+)?/);
3571
+ if (m) {
3572
+ var h = parseInt(m[1]);
3573
+ if (m[3]) {
3574
+ h %= 12;
3575
+ if (m[3].toLowerCase().charAt(0) == 'p') {
3576
+ h += 12;
3577
+ }
3578
+ }
3579
+ return h * 60 + (m[2] ? parseInt(m[2]) : 0);
3580
+ }
3581
+ };
3582
+
3583
+
3584
+
3585
+ /* Date Formatting
3586
+ -----------------------------------------------------------------------------*/
3587
+
3588
+ var formatDate = fc.formatDate = function(date, format, options) {
3589
+ return formatDates(date, null, format, options);
3590
+ };
3591
+
3592
+ var formatDates = fc.formatDates = function(date1, date2, format, options) {
3593
+ options = options || defaults;
3594
+ var date = date1,
3595
+ otherDate = date2,
3596
+ i, len = format.length, c,
3597
+ i2, formatter,
3598
+ res = '';
3599
+ for (i=0; i<len; i++) {
3600
+ c = format.charAt(i);
3601
+ if (c == "'") {
3602
+ for (i2=i+1; i2<len; i2++) {
3603
+ if (format.charAt(i2) == "'") {
3604
+ if (date) {
3605
+ if (i2 == i+1) {
3606
+ res += "'";
3607
+ }else{
3608
+ res += format.substring(i+1, i2);
3609
+ }
3610
+ i = i2;
3611
+ }
3612
+ break;
3613
+ }
3614
+ }
3615
+ }
3616
+ else if (c == '(') {
3617
+ for (i2=i+1; i2<len; i2++) {
3618
+ if (format.charAt(i2) == ')') {
3619
+ var subres = formatDate(date, format.substring(i+1, i2), options);
3620
+ if (parseInt(subres.replace(/\D/, ''))) {
3621
+ res += subres;
3622
+ }
3623
+ i = i2;
3624
+ break;
3625
+ }
3626
+ }
3627
+ }
3628
+ else if (c == '[') {
3629
+ for (i2=i+1; i2<len; i2++) {
3630
+ if (format.charAt(i2) == ']') {
3631
+ var subformat = format.substring(i+1, i2);
3632
+ var subres = formatDate(date, subformat, options);
3633
+ if (subres != formatDate(otherDate, subformat, options)) {
3634
+ res += subres;
3635
+ }
3636
+ i = i2;
3637
+ break;
3638
+ }
3639
+ }
3640
+ }
3641
+ else if (c == '{') {
3642
+ date = date2;
3643
+ otherDate = date1;
3644
+ }
3645
+ else if (c == '}') {
3646
+ date = date1;
3647
+ otherDate = date2;
3648
+ }
3649
+ else {
3650
+ for (i2=len; i2>i; i2--) {
3651
+ if (formatter = dateFormatters[format.substring(i, i2)]) {
3652
+ if (date) {
3653
+ res += formatter(date, options);
3654
+ }
3655
+ i = i2 - 1;
3656
+ break;
3657
+ }
3658
+ }
3659
+ if (i2 == i) {
3660
+ if (date) {
3661
+ res += c;
3662
+ }
3663
+ }
3664
+ }
3665
+ }
3666
+ return res;
3667
+ };
3668
+
3669
+ var dateFormatters = {
3670
+ s : function(d) { return d.getSeconds() },
3671
+ ss : function(d) { return zeroPad(d.getSeconds()) },
3672
+ m : function(d) { return d.getMinutes() },
3673
+ mm : function(d) { return zeroPad(d.getMinutes()) },
3674
+ h : function(d) { return d.getHours() % 12 || 12 },
3675
+ hh : function(d) { return zeroPad(d.getHours() % 12 || 12) },
3676
+ H : function(d) { return d.getHours() },
3677
+ HH : function(d) { return zeroPad(d.getHours()) },
3678
+ d : function(d) { return d.getDate() },
3679
+ dd : function(d) { return zeroPad(d.getDate()) },
3680
+ ddd : function(d,o) { return o.dayNamesShort[d.getDay()] },
3681
+ dddd: function(d,o) { return o.dayNames[d.getDay()] },
3682
+ M : function(d) { return d.getMonth() + 1 },
3683
+ MM : function(d) { return zeroPad(d.getMonth() + 1) },
3684
+ MMM : function(d,o) { return o.monthNamesShort[d.getMonth()] },
3685
+ MMMM: function(d,o) { return o.monthNames[d.getMonth()] },
3686
+ yy : function(d) { return (d.getFullYear()+'').substring(2) },
3687
+ yyyy: function(d) { return d.getFullYear() },
3688
+ t : function(d) { return d.getHours() < 12 ? 'a' : 'p' },
3689
+ tt : function(d) { return d.getHours() < 12 ? 'am' : 'pm' },
3690
+ T : function(d) { return d.getHours() < 12 ? 'A' : 'P' },
3691
+ TT : function(d) { return d.getHours() < 12 ? 'AM' : 'PM' },
3692
+ u : function(d) { return formatDate(d, "yyyy-MM-dd'T'HH:mm:ss'Z'") },
3693
+ S : function(d) {
3694
+ var date = d.getDate();
3695
+ if (date > 10 && date < 20) {
3696
+ return 'th';
3697
+ }
3698
+ return ['st', 'nd', 'rd'][date%10-1] || 'th';
3699
+ }
3700
+ };
3701
+
3702
+
3703
+
3704
+ /* Element Dimensions
3705
+ -----------------------------------------------------------------------------*/
3706
+
3707
+ function setOuterWidth(element, width, includeMargins) {
3708
+ element.each(function(i, _element) {
3709
+ _element.style.width = width - hsides(_element, includeMargins) + 'px';
3710
+ });
3711
+ }
3712
+
3713
+ function setOuterHeight(element, height, includeMargins) {
3714
+ element.each(function(i, _element) {
3715
+ _element.style.height = height - vsides(_element, includeMargins) + 'px';
3716
+ });
3717
+ }
3718
+
3719
+
3720
+ function hsides(_element, includeMargins) {
3721
+ return (parseFloat(jQuery.curCSS(_element, 'paddingLeft', true)) || 0) +
3722
+ (parseFloat(jQuery.curCSS(_element, 'paddingRight', true)) || 0) +
3723
+ (parseFloat(jQuery.curCSS(_element, 'borderLeftWidth', true)) || 0) +
3724
+ (parseFloat(jQuery.curCSS(_element, 'borderRightWidth', true)) || 0) +
3725
+ (includeMargins ? hmargins(_element) : 0);
3726
+ }
3727
+
3728
+ function hmargins(_element) {
3729
+ return (parseFloat(jQuery.curCSS(_element, 'marginLeft', true)) || 0) +
3730
+ (parseFloat(jQuery.curCSS(_element, 'marginRight', true)) || 0);
3731
+ }
3732
+
3733
+ function vsides(_element, includeMargins) {
3734
+ return (parseFloat(jQuery.curCSS(_element, 'paddingTop', true)) || 0) +
3735
+ (parseFloat(jQuery.curCSS(_element, 'paddingBottom', true)) || 0) +
3736
+ (parseFloat(jQuery.curCSS(_element, 'borderTopWidth', true)) || 0) +
3737
+ (parseFloat(jQuery.curCSS(_element, 'borderBottomWidth', true)) || 0) +
3738
+ (includeMargins ? vmargins(_element) : 0);
3739
+ }
3740
+
3741
+ function vmargins(_element) {
3742
+ return (parseFloat(jQuery.curCSS(_element, 'marginTop', true)) || 0) +
3743
+ (parseFloat(jQuery.curCSS(_element, 'marginBottom', true)) || 0);
3744
+ }
3745
+
3746
+
3747
+
3748
+
3749
+ function setMinHeight(element, h) {
3750
+ h = typeof h == 'number' ? h + 'px' : h;
3751
+ element[0].style.cssText += ';min-height:' + h + ';_height:' + h;
3752
+ }
3753
+
3754
+
3755
+
3756
+ /* Position Calculation
3757
+ -----------------------------------------------------------------------------*/
3758
+ // nasty bugs in opera 9.25
3759
+ // position()'s top returning incorrectly with TR/TD or elements within TD
3760
+
3761
+ var topBug;
3762
+
3763
+ function topCorrect(tr) { // tr/th/td or anything else
3764
+ if (topBug !== false) {
3765
+ var cell;
3766
+ if (tr.is('th,td')) {
3767
+ tr = (cell = tr).parent();
3768
+ }
3769
+ if (topBug === undefined && tr.is('tr')) {
3770
+ topBug = tr.position().top != tr.children().position().top;
3771
+ }
3772
+ if (topBug) {
3773
+ return tr.parent().position().top + (cell ? tr.position().top - cell.position().top : 0);
3774
+ }
3775
+ }
3776
+ return 0;
3777
+ }
3778
+
3779
+
3780
+
3781
+ /* Hover Matrix
3782
+ -----------------------------------------------------------------------------*/
3783
+
3784
+ function HoverMatrix(rowElements, colElements, changeCallback) {
3785
+
3786
+ var t=this,
3787
+ tops=[], lefts=[],
3788
+ origRow, origCol,
3789
+ currRow, currCol,
3790
+ e;
3791
+
3792
+ $.each(rowElements, function(i, _e) {
3793
+ e = $(_e);
3794
+ tops.push(e.offset().top + topCorrect(e));
3795
+ });
3796
+ tops.push(tops[tops.length-1] + e.outerHeight());
3797
+ $.each(colElements, function(i, _e) {
3798
+ e = $(_e);
3799
+ lefts.push(e.offset().left);
3800
+ });
3801
+ lefts.push(lefts[lefts.length-1] + e.outerWidth());
3802
+
3803
+
3804
+ t.mouse = function(ev) {
3805
+ var x = ev.pageX;
3806
+ var y = ev.pageY;
3807
+ var r, c;
3808
+ for (r=0; r<tops.length && y>=tops[r]; r++) {}
3809
+ for (c=0; c<lefts.length && x>=lefts[c]; c++) {}
3810
+ r = r >= tops.length ? -1 : r - 1;
3811
+ c = c >= lefts.length ? -1 : c - 1;
3812
+ if (r != currRow || c != currCol) {
3813
+ currRow = r;
3814
+ currCol = c;
3815
+ if (r == -1 || c == -1) {
3816
+ t.cell = null;
3817
+ }else{
3818
+ if (origRow === undefined) {
3819
+ origRow = r;
3820
+ origCol = c;
3821
+ }
3822
+ t.cell = {
3823
+ row: r,
3824
+ col: c,
3825
+ top: tops[r],
3826
+ left: lefts[c],
3827
+ width: lefts[c+1] - lefts[c],
3828
+ height: tops[r+1] - tops[r],
3829
+ origRow: origRow,
3830
+ origCol: origCol,
3831
+ isOrig: r==origRow && c==origCol,
3832
+ rowDelta: r-origRow,
3833
+ colDelta: c-origCol
3834
+ };
3835
+ }
3836
+ changeCallback(t.cell);
3837
+ }
3838
+ };
3839
+
3840
+ t.rect = function(row0, col0, row1, col1, originElement) { // row1,col1 are exclusive
3841
+ var origin = originElement.offset();
3842
+ return {
3843
+ top: tops[row0] - origin.top,
3844
+ left: lefts[col0] - origin.left,
3845
+ width: lefts[col1] - lefts[col0],
3846
+ height: tops[row1] - tops[row0]
3847
+ };
3848
+ };
3849
+
3850
+ }
3851
+
3852
+
3853
+
3854
+ /* Misc Utils
3855
+ -----------------------------------------------------------------------------*/
3856
+
3857
+ var undefined,
3858
+ dayIDs = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat'];
3859
+
3860
+ function zeroPad(n) {
3861
+ return (n < 10 ? '0' : '') + n;
3862
+ }
3863
+
3864
+ function smartProperty(obj, name) { // get a camel-cased/namespaced property of an object
3865
+ if (obj[name] !== undefined) {
3866
+ return obj[name];
3867
+ }
3868
+ var parts = name.split(/(?=[A-Z])/),
3869
+ i=parts.length-1, res;
3870
+ for (; i>=0; i--) {
3871
+ res = obj[parts[i].toLowerCase()];
3872
+ if (res !== undefined) {
3873
+ return res;
3874
+ }
3875
+ }
3876
+ return obj[''];
3877
+ }
3878
+
3879
+ function htmlEscape(s) {
3880
+ return s
3881
+ .replace(/&/g, '&amp;')
3882
+ .replace(/</g, '&lt;')
3883
+ .replace(/>/g, '&gt;')
3884
+ .replace(/'/g, '&#039;')
3885
+ .replace(/"/g, '&quot;');
3886
+ }
3887
+
3888
+
3889
+
3890
+ function HorizontalPositionCache(getElement) {
3891
+
3892
+ var t = this,
3893
+ elements = {},
3894
+ lefts = {},
3895
+ rights = {};
3896
+
3897
+ function e(i) {
3898
+ return elements[i] = elements[i] || getElement(i);
3899
+ }
3900
+
3901
+ t.left = function(i) {
3902
+ return lefts[i] = lefts[i] === undefined ? e(i).position().left : lefts[i];
3903
+ };
3904
+
3905
+ t.right = function(i) {
3906
+ return rights[i] = rights[i] === undefined ? t.left(i) + e(i).width() : rights[i];
3907
+ };
3908
+
3909
+ t.clear = function() {
3910
+ elements = {};
3911
+ lefts = {};
3912
+ rights = {};
3913
+ };
3914
+
3915
+ }
3916
+
3917
+
3918
+
3919
+ function cssKey(_element) {
3920
+ return _element.id + '/' + _element.className + '/' + _element.style.cssText.replace(/(^|;)\s*(top|left|width|height)\s*:[^;]*/ig, '');
3921
+ }
3922
+
3923
+
3924
+
3925
+ function cmp(a, b) {
3926
+ return a - b;
3927
+ }
3928
+
3929
+
3930
+
3931
+ function exclEndDay(event) {
3932
+ if (event.end) {
3933
+ return _exclEndDay(event.end, event.allDay);
3934
+ }else{
3935
+ return addDays(cloneDate(event.start), 1);
3936
+ }
3937
+ }
3938
+
3939
+ function _exclEndDay(end, allDay) {
3940
+ end = cloneDate(end);
3941
+ return allDay || end.getHours() || end.getMinutes() ? addDays(end, 1) : end;
3942
+ }
3943
+
3944
+
3945
+
3946
+ function disableTextSelection(element) {
3947
+ element
3948
+ .attr('unselectable', 'on')
3949
+ .css('MozUserSelect', 'none')
3950
+ .bind('selectstart.ui', function() { return false; });
3951
+ }
3952
+
3953
+ /*
3954
+ function enableTextSelection(element) {
3955
+ element
3956
+ .attr('unselectable', 'off')
3957
+ .css('MozUserSelect', '')
3958
+ .unbind('selectstart.ui');
3959
+ }
3960
+ */
3961
+
3962
+
3963
+
3964
+
3965
+ })(jQuery);