wice_grid 3.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGELOG +412 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +1179 -0
  5. data/Rakefile +42 -0
  6. data/SAVED_QUERIES_HOWTO.rdoc +123 -0
  7. data/VERSION +1 -0
  8. data/lib/generators/wice_grid/templates/calendarview.css +107 -0
  9. data/lib/generators/wice_grid/templates/calendarview.js +1168 -0
  10. data/lib/generators/wice_grid/templates/icons/arrow_down.gif +0 -0
  11. data/lib/generators/wice_grid/templates/icons/arrow_up.gif +0 -0
  12. data/lib/generators/wice_grid/templates/icons/calendar_view_month.png +0 -0
  13. data/lib/generators/wice_grid/templates/icons/delete.png +0 -0
  14. data/lib/generators/wice_grid/templates/icons/expand.png +0 -0
  15. data/lib/generators/wice_grid/templates/icons/page_white_excel.png +0 -0
  16. data/lib/generators/wice_grid/templates/icons/page_white_find.png +0 -0
  17. data/lib/generators/wice_grid/templates/icons/table.png +0 -0
  18. data/lib/generators/wice_grid/templates/icons/table_refresh.png +0 -0
  19. data/lib/generators/wice_grid/templates/icons/tick_all.png +0 -0
  20. data/lib/generators/wice_grid/templates/icons/untick_all.png +0 -0
  21. data/lib/generators/wice_grid/templates/wice_grid.css +173 -0
  22. data/lib/generators/wice_grid/templates/wice_grid.yml +279 -0
  23. data/lib/generators/wice_grid/templates/wice_grid_config.rb +154 -0
  24. data/lib/generators/wice_grid/templates/wice_grid_jquery.js +161 -0
  25. data/lib/generators/wice_grid/templates/wice_grid_prototype.js +153 -0
  26. data/lib/generators/wice_grid/wice_grid_assets_jquery_generator.rb +32 -0
  27. data/lib/generators/wice_grid/wice_grid_assets_prototype_generator.rb +34 -0
  28. data/lib/grid_output_buffer.rb +52 -0
  29. data/lib/grid_renderer.rb +535 -0
  30. data/lib/helpers/js_calendar_helpers.rb +183 -0
  31. data/lib/helpers/wice_grid_misc_view_helpers.rb +113 -0
  32. data/lib/helpers/wice_grid_serialized_queries_view_helpers.rb +91 -0
  33. data/lib/helpers/wice_grid_view_helpers.rb +781 -0
  34. data/lib/js_adaptors/jquery_adaptor.rb +145 -0
  35. data/lib/js_adaptors/js_adaptor.rb +12 -0
  36. data/lib/js_adaptors/prototype_adaptor.rb +168 -0
  37. data/lib/table_column_matrix.rb +51 -0
  38. data/lib/tasks/wice_grid_tasks.rake +28 -0
  39. data/lib/view_columns.rb +486 -0
  40. data/lib/views/create.rjs +13 -0
  41. data/lib/views/create_jq.rjs +31 -0
  42. data/lib/views/delete.rjs +12 -0
  43. data/lib/views/delete_jq.rjs +26 -0
  44. data/lib/wice_grid.rb +827 -0
  45. data/lib/wice_grid_controller.rb +165 -0
  46. data/lib/wice_grid_core_ext.rb +179 -0
  47. data/lib/wice_grid_misc.rb +98 -0
  48. data/lib/wice_grid_serialized_queries_controller.rb +86 -0
  49. data/lib/wice_grid_serialized_query.rb +15 -0
  50. data/lib/wice_grid_spreadsheet.rb +33 -0
  51. data/test/.gitignore +2 -0
  52. data/test/database.yml +21 -0
  53. data/test/schema.rb +33 -0
  54. data/test/test_helper.rb +89 -0
  55. data/test/views/projects_and_people_grid.html.erb +12 -0
  56. data/test/views/projects_and_people_grid_invalid.html.erb +12 -0
  57. data/test/views/simple_projects_grid.html.erb +9 -0
  58. data/test/wice_grid_core_ext_test.rb +183 -0
  59. data/test/wice_grid_functional_test.rb +68 -0
  60. data/test/wice_grid_misc_test.rb +41 -0
  61. data/test/wice_grid_test.rb +42 -0
  62. data/test/wice_grid_view_helper_test.rb +12 -0
  63. data/wice_grid.gemspec +111 -0
  64. metadata +153 -0
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run unit tests.'
6
+ task :default => :test
7
+
8
+ desc 'Test the wice_grid plugin.'
9
+ Rake::TestTask.new(:test) do |t|
10
+ t.libs << 'lib'
11
+ t.pattern = 'test/**/*_test.rb'
12
+ t.verbose = true
13
+ end
14
+
15
+ desc 'Generate documentation for the wice_grid plugin.'
16
+ Rake::RDocTask.new(:rdoc) do |rdoc|
17
+ rdoc.rdoc_dir = 'rdoc'
18
+ rdoc.title = 'WiceGrid'
19
+ rdoc.options << '--line-numbers' << '--inline-source'
20
+ rdoc.rdoc_files.include('README.rdoc')
21
+ rdoc.rdoc_files.include('SAVED_QUERIES_HOWTO.rdoc')
22
+ rdoc.rdoc_files.include('CHANGELOG')
23
+ rdoc.rdoc_files.include('lib/**/*.rb')
24
+ end
25
+
26
+ begin
27
+ require 'jeweler'
28
+ Jeweler::Tasks.new do |gem|
29
+ gem.name = "wice_grid"
30
+ gem.summary = %Q{Rails Grid Plugin}
31
+ gem.description = %Q{A Rails grid plugin to create grids with sorting, pagination, and (automatically generated) filters }
32
+ gem.email = "yuri.leikind@gmail.com"
33
+ gem.homepage = "http://github.com/lekind/wice_grid"
34
+ gem.authors = ["Yuri Leikind"]
35
+ gem.add_development_dependency "will_paginate", ">= 3.0.pre2"
36
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
37
+ end
38
+ Jeweler::GemcutterTasks.new
39
+ rescue LoadError
40
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
41
+ end
42
+
@@ -0,0 +1,123 @@
1
+ == Saving Queries How-To
2
+
3
+ WiceGrid allows to save the state of filters as a custom query and later restore it from the list of saved queries.
4
+
5
+ === Step 1: Create the database table to store queries
6
+
7
+ To get started really fast create the database table to store queries with the following command:
8
+
9
+ rake wice_grid:create_queries_table
10
+
11
+ This will create the table, but it is preferred to manually create and run a migration in <tt>db/migrate</tt>:
12
+
13
+ class CreateWiceGridSerializedQueriesTable < ActiveRecord::Migration
14
+ def self.up
15
+ create_table :wice_grid_serialized_queries do |t|
16
+ t.column :name, :string
17
+ t.column :grid_name, :string
18
+ t.column :query, :text
19
+
20
+ t.timestamps
21
+ end
22
+ add_index :wice_grid_serialized_queries, :grid_name
23
+ add_index :wice_grid_serialized_queries, [:grid_name, :id]
24
+ end
25
+
26
+ def self.down
27
+ drop_table :wice_grid_serialized_queries
28
+ end
29
+ end
30
+
31
+ === Step 2: Create the controller to handle AJAX queries.
32
+
33
+ Creation and deletion of queries is implemented as AJAX calls, and a controller is needed to handle these calls. Create an empty controller
34
+ with any name and add method +save_wice_grid_queries+ to it:
35
+
36
+ class QueriesController < ApplicationController
37
+ save_wice_grid_queries
38
+ end
39
+
40
+ This is it. The controller now has the required action methods.
41
+
42
+ === Step 3: Add routes
43
+
44
+ If the name of the query controller is QueriesController, add the following to <tt>routes.rb</tt>:
45
+
46
+ Wice::define_routes(map, 'queries')
47
+
48
+ === Step 4: Add the saved query panel to the view.
49
+
50
+ To show the list of saved queries and the form to create new queries in a view, add the following helper to the view:
51
+
52
+ <%= saved_queries_panel(@grid_object) %>
53
+
54
+ Voila!
55
+
56
+ Just like WiceGrid itself, the query panel contains no forms and is thus form-friendly.
57
+
58
+ *Important*: Saved queries of all grids in the application are stored in one table and differentiated by the name of the grid, thus, for all forms
59
+ with saved queries it is important to define different names! (use parameter <tt>:name</tt> in +initialize_grid+)
60
+
61
+ It is also possible to initialize a grid with an initial saved query providing the id of the query record or the ActiveRecord
62
+ itself to parameter <tt>saved_query</tt>:
63
+
64
+ @products_grid = initialize_grid(Product,
65
+ :name => 'prod_grid',
66
+ :saved_query => SavedQuery.find_by_id_and_grid_name(12, 'prod_grid') )
67
+
68
+
69
+ == Adding Application Specific Logic to Saving/Restoring Queries
70
+
71
+ WiceGrid allows to add application specific logic to saving and restoring queries by substituting the default ActiveRecord model provided by WiceGrid with a custom model.
72
+
73
+ Copy <tt>vendor/plugins/wice_grid/lib/wice_grid_serialized_query.rb</tt> to <tt>app/models/</tt>, rename the file and the class to your liking.
74
+
75
+ Next, modify the model to suit your needs. Right after copying and renaming the model to SavedQuery it looks like this:
76
+
77
+ class SavedQuery < ActiveRecord::Base #:nodoc:
78
+ serialize :query
79
+
80
+ validates_uniqueness_of :name, :scope => :grid_name, :on => :create, :message => ::Wice::Defaults::VALIDATES_UNIQUENESS_ERROR
81
+ validates_presence_of :name, :message => ::Wice::Defaults::VALIDATES_PRESENCE_ERROR
82
+
83
+ def self.list(name, controller)
84
+ conditions = {:grid_name => name}
85
+ self.find(:all, :conditions => conditions)
86
+ end
87
+ end
88
+
89
+ It is required that the model provides class method +list+ which accepts two parameters: the name of the WiceGrid instance and the controller
90
+ object, and returns a list of queries. The controller object is needed to provide the application context. For instance, if it is needed to
91
+ store queries for each user, we could add +user_id+ to the table and modify the code so it looks like the following:
92
+
93
+ class SavedQuery < ActiveRecord::Base
94
+ serialize :query
95
+
96
+ # the scope is changed to include user_id
97
+ validates_uniqueness_of :name, :scope => [:grid_name, :user_id], :on => :create, :message => 'A query with this name already exists'
98
+ validates_presence_of :name, :message => 'Please submit the name of the query'
99
+
100
+ belongs_to :identity # !
101
+
102
+ def self.list(name, controller)
103
+ conditions = {:grid_name => name}
104
+ if controller.current_user # !
105
+ conditions[:user_id] = controller.current_user.id # provided that method current_user is defined in ApplicationController and returns the currrent user.
106
+ end
107
+ self.find(:all, :conditions => conditions)
108
+ end
109
+ end
110
+
111
+ The following step is to make sure that a new query is saved with the correct +user_id+. To do so, change the helper
112
+ <tt>saved_queries_panel(@grid_object)</tt> to the following:
113
+
114
+
115
+ <%= saved_queries_panel(@identities_grid, :extra_parameters => {:user_id => @current_user.id}) %>
116
+
117
+ For every key in has :extra_parameters there must exist a field in the model - this hash will be used as a parameter to
118
+ <tt>attributes=</tt> method of the query object.
119
+
120
+ Finally, let WiceGrid know which model to use for saving queries by changing constant +QUERY_STORE_MODEL+
121
+ in <tt>lib/wice_grid_config.rb</tt> to the name of the custom model (as a string), in the above example this would look like the following:
122
+
123
+ QUERY_STORE_MODEL = 'SavedQuery'
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 3.0.0.pre1
@@ -0,0 +1,107 @@
1
+
2
+ div.calendar
3
+ {
4
+ font-size: smaller;
5
+ color: #000;
6
+ z-index: 5;
7
+ }
8
+
9
+ div.calendar.popup
10
+ {
11
+ margin-left: -40px;
12
+ margin-top: -100px;
13
+ }
14
+
15
+ div.calendar table
16
+ {
17
+ background-color: #eee;
18
+ border: 1px solid #aaa;
19
+ border-collapse: collapse;
20
+ }
21
+
22
+ div.calendar thead {
23
+ background-color: white;
24
+ }
25
+
26
+ div.calendar td,
27
+ div.calendar th
28
+ {
29
+ padding: 3px;
30
+ text-align: center;
31
+ }
32
+
33
+ div.calendar td.title
34
+ {
35
+ font-weight: bold;
36
+ }
37
+
38
+ div.calendar th
39
+ {
40
+ background: #ddd;
41
+ border-bottom: 1px solid #ccc;
42
+ border-top: 1px solid #ccc;
43
+ font-weight: bold;
44
+ color: #555;
45
+ }
46
+
47
+ div.calendar tr.days td {
48
+ width: 2em;
49
+ color: #555;
50
+ text-align: center;
51
+ cursor: pointer;
52
+ }
53
+
54
+ div.calendar tr.days td:hover,
55
+ div.calendar td.cvbutton:hover
56
+ {
57
+ background-color: #34ABFA;
58
+ cursor: pointer;
59
+ }
60
+
61
+
62
+ div.calendar tr td.closeButton:hover
63
+ {
64
+ background-color: #34ABFA;
65
+ cursor: pointer;
66
+ }
67
+
68
+
69
+ div.calendar tr.days td:active
70
+ div.calendar td.cvbutton:active
71
+ {
72
+ background-color: #cde;
73
+ }
74
+
75
+ div.calendar tr.days td.selected
76
+ {
77
+ font-weight: bold;
78
+ background-color: #fff;
79
+ color: #000;
80
+ }
81
+
82
+ div.calendar tr.days td.today
83
+ {
84
+ font-weight: bold;
85
+ color: #D50000;
86
+ }
87
+
88
+ div.calendar tr.days td.otherDay
89
+ {
90
+ color: #bbb;
91
+ }
92
+
93
+ div.calendar .draggableHandler{
94
+ cursor: move;
95
+ }
96
+
97
+ /* styles for the date_picker Rails plugin */
98
+ span.date_picker a.date_label{
99
+ margin-left: 5px;
100
+ margin-right: 5px;
101
+ text-decoration: none;
102
+ }
103
+ span.date_picker a.date_label:hover{text-decoration: line-through;}
104
+
105
+ span.date_picker span.trigger:hover{
106
+ cursor: pointer;
107
+ }
@@ -0,0 +1,1168 @@
1
+ //
2
+ // CalendarView (for Prototype)
3
+ // calendarview.org
4
+ //
5
+ // Maintained by Justin Mecham <justin@aspect.net>
6
+ //
7
+ // Portions Copyright 2002-2005 Mihai Bazon
8
+ //
9
+ // This calendar is based very loosely on the Dynarch Calendar in that it was
10
+ // used as a base, but completely gutted and more or less rewritten in place
11
+ // to use the Prototype JavaScript library.
12
+ //
13
+ // As such, CalendarView is licensed under the terms of the GNU Lesser General
14
+ // Public License (LGPL). More information on the Dynarch Calendar can be
15
+ // found at:
16
+ //
17
+ // www.dynarch.com/projects/calendar
18
+ //
19
+
20
+
21
+ /* This fork by Yuri Leikind ( git://github.com/leikind/calendarview.git ) adds a number of features.
22
+
23
+ The differences from the original are
24
+
25
+ * Support for time in the form of two dropdowns for hours and minutes. Can be turned off/on.
26
+ * Draggable popup calendars (which introduces new dependancies: script.aculo.us effects.js and dragdrop.js)
27
+ * Close button
28
+ * Ability to unset the date by clicking on the active date
29
+ * Simple I18n support
30
+ * Removed all ambiguity in the API
31
+ * Two strategies in positioning of popup calendars: relative to the popup trigger element (original behavior),
32
+ and is relative to the mouse pointer (can be configured)
33
+ * Popup calendars are not created every time they pop up, on the contrary, they are created once just like
34
+ embedded calendars, and then shown or hidden.
35
+ * Possible to have many popup calendars on page. The behavior of the original calendarview when a popup
36
+ calendar is hidden when the user clicks elsewhere on the page is an option now.
37
+ * Refactoring and changes to the OO design like getting rid of Calendar.prototype in favor of class based
38
+ OO provided by OO, and getting rid of Calendar.setup({}) in favor of a simple object constructor new Calendar({}).
39
+
40
+ */
41
+
42
+ var Calendar = Class.create({
43
+
44
+ container: null,
45
+
46
+ minYear: 1900,
47
+ maxYear: 2100,
48
+
49
+ date: new Date(),
50
+ currentDateElement: null,
51
+
52
+ shouldClose: false,
53
+ isPopup: true,
54
+
55
+ initialize: function(params){
56
+
57
+ if (! Calendar.init_done){
58
+ Calendar.init();
59
+ }
60
+
61
+ embedAt = params.embedAt || null;
62
+ withTime = params.withTime || null;
63
+ dateFormat = params.dateFormat || null;
64
+ initialDate = params.initialDate || null;
65
+ popupTriggerElement = params.popupTriggerElement || null;
66
+ this.onHideCallback = params.onHideCallback || function(date, calendar){};
67
+ this.onDateChangedCallback = params.onDateChangedCallback || function(date, calendar){};
68
+ this.minuteStep = params.minuteStep || 5;
69
+ this.hideOnClickOnDay = params.hideOnClickOnDay || false;
70
+ this.hideOnClickElsewhere = params.hideOnClickElsewhere || false;
71
+ this.outputFields = params.outputFields || $A();
72
+ this.popupPositioningStrategy = params.popupPositioningStrategy || 'trigger'; // or 'pointer'
73
+ this.x = params.x || 0;
74
+ this.y = params.y || 0.6;
75
+
76
+ this.outputFields = $A(this.outputFields).collect(function(f){
77
+ return $(f);
78
+ });
79
+
80
+ if (embedAt){
81
+ this.embedAt = $(embedAt);
82
+ this.embedAt._calendar = this;
83
+ }else{
84
+ this.embedAt = null;
85
+ }
86
+
87
+ this.withTime = withTime;
88
+
89
+ if (dateFormat){
90
+ this.dateFormat = dateFormat;
91
+ }else{
92
+ if(this.withTime){
93
+ this.dateFormat = Calendar.defaultDateTimeFormat;
94
+ }else{
95
+ this.dateFormat = Calendar.defaultDateFormat;
96
+ }
97
+ }
98
+
99
+ this.dateFormatForHiddenField = params.dateFormatForHiddenField || this.dateFormat;
100
+
101
+
102
+ if (initialDate) {
103
+ this.date = this.parseDate(initialDate);
104
+ }
105
+
106
+ this.build();
107
+
108
+ if (this.isPopup) { //Popup Calendars
109
+ var popupTriggerElement = $(popupTriggerElement);
110
+ popupTriggerElement._calendar = this;
111
+
112
+ popupTriggerElement.observe('click', function(event){
113
+ this.showAtElement(event, popupTriggerElement);
114
+ }.bind(this) );
115
+
116
+ } else{ // In-Page Calendar
117
+ this.show();
118
+ }
119
+
120
+ if (params.updateOuterFieldsOnInit){
121
+ this.updateOuterFieldWithoutCallback(); // Just for the sake of localization and DatePicker
122
+ }
123
+ },
124
+
125
+ build: function(){
126
+ if (this.embedAt) {
127
+ var parentForCalendarTable = this.embedAt;
128
+ this.isPopup = false;
129
+ } else {
130
+ var parentForCalendarTable = document.getElementsByTagName('body')[0];
131
+ this.isPopup = true;
132
+ }
133
+
134
+
135
+ var table = new Element('table');
136
+
137
+ var thead = new Element('thead');
138
+ table.appendChild(thead);
139
+
140
+ var firstRow = new Element('tr');
141
+
142
+ if (this.isPopup){
143
+ var cell = new Element('td');
144
+ cell.addClassName('draggableHandler');
145
+ firstRow.appendChild(cell);
146
+
147
+ cell = new Element('td', { colSpan: 5 });
148
+ cell.addClassName('title' );
149
+ cell.addClassName('draggableHandler');
150
+ firstRow.appendChild(cell);
151
+
152
+ cell = new Element('td');
153
+ cell.addClassName('closeButton');
154
+ firstRow.appendChild(cell);
155
+ cell.update('x');
156
+
157
+ cell.observe('mousedown', function(){
158
+ this.hide();
159
+ }.bind(this));
160
+
161
+
162
+ }else{
163
+ var cell = new Element('td', { colSpan: 7 } );
164
+ firstRow.appendChild(cell);
165
+ }
166
+
167
+ cell.addClassName('title');
168
+
169
+ thead.appendChild(firstRow);
170
+
171
+ var row = new Element('tr')
172
+ this._drawButtonCell(row, '&#x00ab;', 1, Calendar.NAV_PREVIOUS_YEAR);
173
+ this._drawButtonCell(row, '&#x2039;', 1, Calendar.NAV_PREVIOUS_MONTH);
174
+ this._drawButtonCell(row, Calendar.getMessageFor('today'), 3, Calendar.NAV_TODAY);
175
+ this._drawButtonCell(row, '&#x203a;', 1, Calendar.NAV_NEXT_MONTH);
176
+ this._drawButtonCell(row, '&#x00bb;', 1, Calendar.NAV_NEXT_YEAR);
177
+ thead.appendChild(row)
178
+
179
+ // Day Names
180
+ row = new Element('tr');
181
+ for (var i = 0; i < 7; ++i) {
182
+ cell = new Element('th').update(Calendar.SHORT_DAY_NAMES[i]);
183
+ if (i == 0 || i == 6){
184
+ cell.addClassName('weekend');
185
+ }
186
+ row.appendChild(cell);
187
+ }
188
+ thead.appendChild(row);
189
+
190
+ // Calendar Days
191
+ var tbody = table.appendChild(new Element('tbody'));
192
+ for (i = 6; i > 0; --i) {
193
+ row = tbody.appendChild(new Element('tr'));
194
+ row.addClassName('days');
195
+ for (var j = 7; j > 0; --j) {
196
+ cell = row.appendChild(new Element('td'));
197
+ cell.calendar = this;
198
+ }
199
+ }
200
+
201
+ // Time Placeholder
202
+ if (this.withTime){
203
+ var tfoot = table.appendChild(new Element('tfoot'));
204
+ row = tfoot.appendChild(new Element('tr'));
205
+ cell = row.appendChild(new Element('td', { colSpan: 7 }));
206
+ cell.addClassName('time');
207
+ var hourSelect = cell.appendChild(new Element('select', { name : 'hourSelect'}));
208
+ for (var i = 0; i < 24; i++) {
209
+ hourSelect.appendChild(new Element('option', {value : i}).update(i));
210
+ }
211
+ this.hourSelect = hourSelect;
212
+
213
+ cell.appendChild(new Element('span')).update(' : ');
214
+
215
+ var minuteSelect = cell.appendChild(new Element('select', { name : 'minuteSelect'}));
216
+ for (var i = 0; i < 60; i += this.minuteStep) {
217
+ minuteSelect.appendChild(new Element('option', {value : i}).update(i));
218
+ }
219
+ this.minuteSelect = minuteSelect;
220
+
221
+ hourSelect.observe('change', function(event){
222
+ if (! this.date) return;
223
+ var elem = event.element();
224
+ var selectedIndex = elem.selectedIndex;
225
+ if ((typeof selectedIndex != 'undefined') && selectedIndex != null){
226
+ this.date.setHours(elem.options[selectedIndex].value);
227
+ this.updateOuterField();
228
+ }
229
+ }.bind(this));
230
+
231
+ minuteSelect.observe('change', function(event){
232
+ if (! this.date) return;
233
+ var elem = event.element();
234
+ var selectedIndex = elem.selectedIndex;
235
+ if ((typeof selectedIndex != 'undefined') && selectedIndex != null){
236
+ this.date.setMinutes(elem.options[selectedIndex].value);
237
+ this.updateOuterField();
238
+ }
239
+ }.bind(this))
240
+ }
241
+
242
+ // Calendar Container (div)
243
+ this.container = new Element('div');
244
+ this.container.addClassName('calendar');
245
+ if (this.isPopup) {
246
+ this.container.setStyle({ position: 'absolute', display: 'none' });
247
+ this.container.addClassName('popup');
248
+ }
249
+ this.container.appendChild(table);
250
+
251
+ this.update(this.date);
252
+
253
+ Event.observe(this.container, 'mousedown', Calendar.handleMouseDownEvent);
254
+
255
+ parentForCalendarTable.appendChild(this.container);
256
+
257
+ if (this.isPopup){
258
+ new Draggable(table, {handle : firstRow });
259
+ }
260
+ },
261
+
262
+ updateOuterFieldReal: function(element){
263
+ if (element.tagName == 'DIV' || element.tagName == 'SPAN') {
264
+ formatted = this.date ? this.date.print(this.dateFormat) : '';
265
+ element.update(formatted);
266
+ } else if (element.tagName == 'INPUT') {
267
+ formatted = this.date ? this.date.print(this.dateFormatForHiddenField) : '';
268
+ element.value = formatted;
269
+ }
270
+ },
271
+
272
+ updateOuterFieldWithoutCallback: function(){
273
+ this.outputFields.each(function(field){
274
+ this.updateOuterFieldReal(field);
275
+ }.bind(this));
276
+ },
277
+
278
+ updateOuterField: function(){
279
+ this.updateOuterFieldWithoutCallback();
280
+ this.onDateChangedCallback(this.date, this);
281
+ },
282
+
283
+ viewOutputFields: function(){
284
+ return this.outputFields.findAll(function(element){
285
+ if (element.tagName == 'DIV' || element.tagName == 'SPAN'){
286
+ return true;
287
+ }else{
288
+ return false;
289
+ }
290
+ });
291
+ },
292
+
293
+
294
+ //----------------------------------------------------------------------------
295
+ // Update Calendar
296
+ //----------------------------------------------------------------------------
297
+
298
+ update: function(date) {
299
+
300
+ var today = new Date();
301
+ var thisYear = today.getFullYear();
302
+ var thisMonth = today.getMonth();
303
+ var thisDay = today.getDate();
304
+ var month = date.getMonth();
305
+ var dayOfMonth = date.getDate();
306
+ var hour = date.getHours();
307
+ var minute = date.getMinutes();
308
+
309
+ // Ensure date is within the defined range
310
+ if (date.getFullYear() < this.minYear)
311
+ date.__setFullYear(this.minYear);
312
+ else if (date.getFullYear() > this.maxYear)
313
+ date.__setFullYear(this.maxYear);
314
+
315
+ if (this.isBackedUp()){
316
+ this.dateBackedUp = new Date(date);
317
+ }else{
318
+ this.date = new Date(date);
319
+ }
320
+
321
+ // Calculate the first day to display (including the previous month)
322
+ date.setDate(1)
323
+ date.setDate(-(date.getDay()) + 1)
324
+
325
+ // Fill in the days of the month
326
+ Element.getElementsBySelector(this.container, 'tbody tr').each(
327
+ function(row, i) {
328
+ var rowHasDays = false;
329
+ row.immediateDescendants().each(
330
+ function(cell, j) {
331
+ var day = date.getDate();
332
+ var dayOfWeek = date.getDay();
333
+ var isCurrentMonth = (date.getMonth() == month);
334
+
335
+ // Reset classes on the cell
336
+ cell.className = '';
337
+ cell.date = new Date(date);
338
+ cell.update(day);
339
+
340
+ // Account for days of the month other than the current month
341
+ if (!isCurrentMonth)
342
+ cell.addClassName('otherDay');
343
+ else
344
+ rowHasDays = true;
345
+
346
+ // Ensure the current day is selected
347
+ if ((! this.isBackedUp()) && isCurrentMonth && day == dayOfMonth) {
348
+ cell.addClassName('selected');
349
+ this.currentDateElement = cell;
350
+ }
351
+
352
+ // Today
353
+ if (date.getFullYear() == thisYear && date.getMonth() == thisMonth && day == thisDay)
354
+ cell.addClassName('today');
355
+
356
+ // Weekend
357
+ if ([0, 6].indexOf(dayOfWeek) != -1)
358
+ cell.addClassName('weekend');
359
+
360
+ // Set the date to tommorrow
361
+ date.setDate(day + 1);
362
+ }.bind(this)
363
+ )
364
+ // Hide the extra row if it contains only days from another month
365
+ !rowHasDays ? row.hide() : row.show();
366
+ }.bind(this)
367
+ )
368
+
369
+ Element.getElementsBySelector(this.container, 'tfoot tr td select').each(
370
+ function(sel){
371
+ if(sel.name == 'hourSelect'){
372
+ sel.selectedIndex = hour;
373
+ }else if(sel.name == 'minuteSelect'){
374
+ if (this.minuteStep == 1){
375
+ sel.selectedIndex = minute;
376
+ }else{
377
+ sel.selectedIndex = this.findClosestMinute(minute);
378
+ }
379
+ }
380
+ }.bind(this)
381
+ )
382
+
383
+ this.container.getElementsBySelector('td.title')[0].update(
384
+ Calendar.MONTH_NAMES[month] + ' ' + this.dateOrDateBackedUp().getFullYear()
385
+ )
386
+
387
+ },
388
+
389
+
390
+ findClosestMinute: function(val){
391
+ if (val == 0){
392
+ return 0;
393
+ }
394
+ var lowest = ((val / this.minuteStep).floor() * this.minuteStep);
395
+ var distance = val % this.minuteStep;
396
+ var minuteValueToShow;
397
+
398
+ if (distance <= (this.minuteStep / 2)){
399
+ minuteValueToShow = lowest;
400
+ }else{
401
+ minuteValueToShow = lowest + this.minuteStep;
402
+ }
403
+
404
+ if (minuteValueToShow == 0){
405
+ return minuteValueToShow;
406
+ }else if(minuteValueToShow >= 60){
407
+ return (minuteValueToShow / this.minuteStep).floor() - 1;
408
+ }else{
409
+ return minuteValueToShow / this.minuteStep;
410
+ }
411
+ },
412
+
413
+ _drawButtonCell: function(parentForCell, text, colSpan, navAction) {
414
+ var cell = new Element('td');
415
+ if (colSpan > 1) cell.colSpan = colSpan;
416
+ cell.className = 'cvbutton';
417
+ cell.calendar = this;
418
+ cell.navAction = navAction;
419
+ cell.innerHTML = text;
420
+ cell.unselectable = 'on'; // IE
421
+ parentForCell.appendChild(cell);
422
+ return cell;
423
+ },
424
+
425
+
426
+ //------------------------------------------------------------------------------
427
+ // Calendar Display Functions
428
+ //------------------------------------------------------------------------------
429
+
430
+ show: function(){
431
+ this.container.show();
432
+ if (this.isPopup) {
433
+ if (this.hideOnClickElsewhere){
434
+ window._popupCalendar = this;
435
+ document.observe('mousedown', Calendar._checkCalendar);
436
+ }
437
+ }
438
+ },
439
+
440
+ showAt: function (x, y) {
441
+ this.container.setStyle({ left: x + 'px', top: y + 'px' });
442
+ this.show();
443
+ },
444
+
445
+
446
+ showAtElement: function(event, element) {
447
+ this.container.show();
448
+ var x, y;
449
+ if (this.popupPositioningStrategy == 'pointer'){ // follow the mouse pointer
450
+ var pos = Event.pointer(event);
451
+ var containerWidth = this.container.getWidth();
452
+ x = containerWidth * this.x + pos.x;
453
+ y = containerWidth * this.y + pos.y;
454
+ }else{ // 'container' - container of the trigger elements
455
+ var pos = Position.cumulativeOffset(element);
456
+ x = pos[0];
457
+ y = this.container.offsetHeight * 0.75 + pos[1];
458
+ }
459
+ this.showAt(x, y);
460
+ },
461
+
462
+ hide: function() {
463
+ if (this.isPopup){
464
+ Event.stopObserving(document, 'mousedown', Calendar._checkCalendar);
465
+ }
466
+ this.container.hide();
467
+ this.onHideCallback(this.date, this);
468
+ },
469
+
470
+
471
+ // Tries to identify the date represented in a string. If successful it also
472
+ // calls this.updateIfDateDifferent which moves the calendar to the given date.
473
+ parseDate: function(str, format){
474
+ if (!format){
475
+ format = this.dateFormat;
476
+ }
477
+ var res = Date.parseDate(str, format);
478
+ return res;
479
+ },
480
+
481
+
482
+ dateOrDateBackedUp: function(){
483
+ return this.date || this.dateBackedUp;
484
+ },
485
+
486
+ updateIfDateDifferent: function(date) {
487
+ if (!date.equalsTo(this.dateOrDateBackedUp())){
488
+ this.update(date);
489
+ }
490
+ },
491
+
492
+ backupDateAndCurrentElement: function(){
493
+ if (this.minuteSelect){
494
+ this.minuteSelect.disable();
495
+ }
496
+ if (this.hourSelect){
497
+ this.hourSelect.disable();
498
+ }
499
+
500
+ this.currentDateElementBackedUp = this.currentDateElement;
501
+ this.currentDateElement = null;
502
+
503
+ this.dateBackedUp = this.date;
504
+ this.date = null;
505
+ },
506
+
507
+ restoreDateAndCurrentElement: function(){
508
+ if (this.minuteSelect){
509
+ this.minuteSelect.enable();
510
+ }
511
+ if (this.hourSelect){
512
+ this.hourSelect.enable();
513
+ }
514
+
515
+ this.currentDateElement = this.currentDateElementBackedUp;
516
+ this.currentDateElementBackedUp = null;
517
+
518
+ this.date = this.dateBackedUp;
519
+ this.dateBackedUp = null;
520
+ },
521
+
522
+ isBackedUp: function(){
523
+ return ((this.date == null) && this.dateBackedUp);
524
+ },
525
+
526
+ dumpDates: function(){
527
+ console.log('date: ' + this.date);
528
+ console.log('dateBackedUp: ' + this.dateBackedUp);
529
+ },
530
+
531
+
532
+ setRange: function(minYear, maxYear) {
533
+ this.minYear = minYear;
534
+ this.maxYear = maxYear;
535
+ }
536
+ })
537
+
538
+ // Delete or add new locales from I18n.js according to your needs
539
+ Calendar.messagebundle = $H({'en' :
540
+ $H({
541
+ 'monday' : 'Monday',
542
+ 'tuesday' : 'Tuesday',
543
+ 'wednesday' : 'Wednesday',
544
+ 'thursday' : 'Thursday',
545
+ 'friday' : 'Friday',
546
+ 'saturday' : 'Saturday',
547
+ 'sunday' : 'Sunday',
548
+
549
+ 'monday_short' : 'M',
550
+ 'tuesday_short' : 'T',
551
+ 'wednesday_short' : 'W',
552
+ 'thursday_short' : 'T',
553
+ 'friday_short' : 'F',
554
+ 'saturday_short' : 'S',
555
+ 'sunday_short' : 'S',
556
+
557
+ 'january' : 'January',
558
+ 'february' : 'February',
559
+ 'march' : 'March',
560
+ 'april' : 'April',
561
+ 'may' : 'May',
562
+ 'june' : 'June',
563
+ 'july' : 'July',
564
+ 'august' : 'August',
565
+ 'september' : 'September',
566
+ 'october' : 'October',
567
+ 'november' : 'November',
568
+ 'december' : 'December',
569
+
570
+ 'january_short' : 'Jan',
571
+ 'february_short' : 'Feb',
572
+ 'march_short' : 'Mar',
573
+ 'april_short' : 'Apr',
574
+ 'may_short' : 'May',
575
+ 'june_short' : 'Jun',
576
+ 'july_short' : 'Jul',
577
+ 'august_short' : 'Aug',
578
+ 'september_short' : 'Sep',
579
+ 'october_short' : 'Oct',
580
+ 'november_short' : 'Nov',
581
+ 'december_short' : 'Dec',
582
+
583
+ 'today' : 'Today'
584
+ }),
585
+ 'fr' :
586
+ $H({
587
+ 'monday' : 'Lundi',
588
+ 'tuesday' : 'Mardi',
589
+ 'wednesday' : 'Mercredi',
590
+ 'thursday' : 'Jeudi',
591
+ 'friday' : 'Vendredi',
592
+ 'saturday' : 'Samedi',
593
+ 'sunday' : 'Dimanche',
594
+
595
+ 'monday_short' : 'Lu',
596
+ 'tuesday_short' : 'Ma',
597
+ 'wednesday_short' : 'Me',
598
+ 'thursday_short' : 'Je',
599
+ 'friday_short' : 'Ve',
600
+ 'saturday_short' : 'Sa',
601
+ 'sunday_short' : 'Di',
602
+
603
+ 'january' : 'janvier',
604
+ 'february' : 'février',
605
+ 'march' : 'mars',
606
+ 'april' : 'avril',
607
+ 'may' : 'mai',
608
+ 'june' : 'juin',
609
+ 'july' : 'juillet',
610
+ 'august' : 'août',
611
+ 'september' : 'septembre',
612
+ 'october' : 'octobre',
613
+ 'november' : 'novembre',
614
+ 'december' : 'décembre',
615
+
616
+ 'january_short' : 'jan',
617
+ 'february_short' : 'fév',
618
+ 'march_short' : 'mar',
619
+ 'april_short' : 'avr',
620
+ 'may_short' : 'mai',
621
+ 'june_short' : 'jun',
622
+ 'july_short' : 'jul',
623
+ 'august_short' : 'aoû',
624
+ 'september_short' : 'sep',
625
+ 'october_short' : 'oct',
626
+ 'november_short' : 'nov',
627
+ 'december_short' : 'dec',
628
+
629
+ 'today' : 'aujourd\'hui'
630
+ }),
631
+ 'nl' :
632
+ $H({
633
+ 'monday' : 'maandag',
634
+ 'tuesday' : 'dinsdag',
635
+ 'wednesday' : 'woensdag',
636
+ 'thursday' : 'donderdag',
637
+ 'friday' : 'vrijdag',
638
+ 'saturday' : 'zaterdag',
639
+ 'sunday' : 'zondag',
640
+
641
+ 'monday_short' : 'Ma',
642
+ 'tuesday_short' : 'Di',
643
+ 'wednesday_short' : 'Wo',
644
+ 'thursday_short' : 'Do',
645
+ 'friday_short' : 'Vr',
646
+ 'saturday_short' : 'Za',
647
+ 'sunday_short' : 'Zo',
648
+
649
+ 'january' : 'januari',
650
+ 'february' : 'februari',
651
+ 'march' : 'maart',
652
+ 'april' : 'april',
653
+ 'may' : 'mei',
654
+ 'june' : 'juni',
655
+ 'july' : 'juli',
656
+ 'august' : 'augustus',
657
+ 'september' : 'september',
658
+ 'october' : 'oktober',
659
+ 'november' : 'november',
660
+ 'december' : 'december',
661
+
662
+ 'january_short' : 'jan',
663
+ 'february_short' : 'feb',
664
+ 'march_short' : 'mrt',
665
+ 'april_short' : 'apr',
666
+ 'may_short' : 'mei',
667
+ 'june_short' : 'jun',
668
+ 'july_short' : 'jul',
669
+ 'august_short' : 'aug',
670
+ 'september_short' : 'sep',
671
+ 'october_short' : 'okt',
672
+ 'november_short' : 'nov',
673
+ 'december_short' : 'dec',
674
+
675
+ 'today' : 'vandaag'
676
+ })
677
+ });
678
+
679
+
680
+ Calendar.getMessageFor = function(key){
681
+
682
+ var lang = Calendar.language || 'en';
683
+ if (! Calendar.messagebundle.get(lang)){
684
+ lang = 'en';
685
+ }
686
+ return Calendar.messagebundle.get(lang).get(key);
687
+ };
688
+
689
+ Calendar.VERSION = '1.4';
690
+
691
+ Calendar.defaultDateFormat = '%Y-%m-%d';
692
+ Calendar.defaultDateTimeFormat = '%Y-%m-%d %H:%M';
693
+
694
+ // we need to postpone the initialization of these structures to let the page define the language of the page
695
+ Calendar.init = function(){
696
+
697
+ Calendar.DAY_NAMES = new Array(
698
+ Calendar.getMessageFor('sunday'),
699
+ Calendar.getMessageFor('monday'),
700
+ Calendar.getMessageFor('tuesday'),
701
+ Calendar.getMessageFor('wednesday'),
702
+ Calendar.getMessageFor('thursday'),
703
+ Calendar.getMessageFor('friday'),
704
+ Calendar.getMessageFor('saturday')
705
+ );
706
+
707
+ Calendar.SHORT_DAY_NAMES = new Array(
708
+ Calendar.getMessageFor('sunday_short'),
709
+ Calendar.getMessageFor('monday_short'),
710
+ Calendar.getMessageFor('tuesday_short'),
711
+ Calendar.getMessageFor('wednesday_short'),
712
+ Calendar.getMessageFor('thursday_short'),
713
+ Calendar.getMessageFor('friday_short'),
714
+ Calendar.getMessageFor('saturday_short')
715
+ );
716
+
717
+ Calendar.MONTH_NAMES = new Array(
718
+ Calendar.getMessageFor('january'),
719
+ Calendar.getMessageFor('february'),
720
+ Calendar.getMessageFor('march'),
721
+ Calendar.getMessageFor('april'),
722
+ Calendar.getMessageFor('may'),
723
+ Calendar.getMessageFor('june'),
724
+ Calendar.getMessageFor('july'),
725
+ Calendar.getMessageFor('august'),
726
+ Calendar.getMessageFor('september'),
727
+ Calendar.getMessageFor('october'),
728
+ Calendar.getMessageFor('november'),
729
+ Calendar.getMessageFor('december')
730
+ );
731
+
732
+ Calendar.SHORT_MONTH_NAMES = new Array(
733
+ Calendar.getMessageFor('january_short'),
734
+ Calendar.getMessageFor('february_short'),
735
+ Calendar.getMessageFor('march_short'),
736
+ Calendar.getMessageFor('april_short'),
737
+ Calendar.getMessageFor('may_short'),
738
+ Calendar.getMessageFor('june_short'),
739
+ Calendar.getMessageFor('july_short'),
740
+ Calendar.getMessageFor('august_short'),
741
+ Calendar.getMessageFor('september_short'),
742
+ Calendar.getMessageFor('october_short'),
743
+ Calendar.getMessageFor('november_short'),
744
+ Calendar.getMessageFor('december_short')
745
+ );
746
+ Calendar.init_done = true;
747
+ };
748
+
749
+ Calendar.NAV_PREVIOUS_YEAR = -2;
750
+ Calendar.NAV_PREVIOUS_MONTH = -1;
751
+ Calendar.NAV_TODAY = 0;
752
+ Calendar.NAV_NEXT_MONTH = 1;
753
+ Calendar.NAV_NEXT_YEAR = 2;
754
+
755
+ //------------------------------------------------------------------------------
756
+ // Static Methods
757
+ //------------------------------------------------------------------------------
758
+
759
+ // This gets called when the user presses a mouse button anywhere in the
760
+ // document, if the calendar is shown. If the click was outside the open
761
+ // calendar this function closes it.
762
+ Calendar._checkCalendar = function(event) {
763
+ if (!window._popupCalendar){
764
+ return false;
765
+ }
766
+ if (Element.descendantOf(Event.element(event), window._popupCalendar.container)){
767
+ return;
768
+ }
769
+ Calendar.closeHandler(window._popupCalendar);
770
+ return Event.stop(event);
771
+ }
772
+
773
+ //------------------------------------------------------------------------------
774
+ // Event Handlers
775
+ //------------------------------------------------------------------------------
776
+
777
+ Calendar.handleMouseDownEvent = function(event){
778
+ if (event.element().type == 'select-one'){ // ignore select elements - not escaping this in Safari leaves select boxes non-functional
779
+ return true;
780
+ }
781
+ Event.observe(document, 'mouseup', Calendar.handleMouseUpEvent);
782
+ Event.stop(event)
783
+ }
784
+
785
+ Calendar.handleMouseUpEvent = function(event){
786
+ var el = Event.element(event);
787
+ var calendar = el.calendar;
788
+ var isNewDate = false;
789
+
790
+
791
+ // If the element that was clicked on does not have an associated Calendar
792
+ // object, return as we have nothing to do.
793
+ if (!calendar) return false;
794
+
795
+ // Clicked on a day
796
+ if (typeof el.navAction == 'undefined') {
797
+
798
+ var dateWasDefined = true;
799
+ if (calendar.date == null){
800
+ dateWasDefined = false;
801
+ calendar.restoreDateAndCurrentElement();
802
+ }
803
+
804
+
805
+ if (calendar.currentDateElement) {
806
+ Element.removeClassName(calendar.currentDateElement, 'selected');
807
+
808
+ if (dateWasDefined && el == calendar.currentDateElement){
809
+ calendar.backupDateAndCurrentElement();
810
+
811
+ calendar.updateOuterField();
812
+
813
+ Event.stopObserving(document, 'mouseup', Calendar.handleMouseUpEvent);
814
+ return Event.stop(event);
815
+ }
816
+
817
+ Element.addClassName(el, 'selected');
818
+
819
+ calendar.shouldClose = (calendar.currentDateElement == el);
820
+
821
+ if (!calendar.shouldClose) {
822
+
823
+ calendar.currentDateElement = el;
824
+ }
825
+ }
826
+ calendar.date.setDateOnly(el.date);
827
+ isNewDate = true;
828
+
829
+ calendar.shouldClose = !el.hasClassName('otherDay');
830
+
831
+
832
+ var isOtherMonth = !calendar.shouldClose;
833
+ if (isOtherMonth) {
834
+ calendar.update(calendar.date);
835
+ }
836
+
837
+ if (! calendar.hideOnClickOnDay){ // override closing if calendar.hideOnClickOnDay is false
838
+ calendar.shouldClose = false;
839
+ }
840
+
841
+ } else { // Clicked on an action button
842
+
843
+ var date = new Date(calendar.dateOrDateBackedUp());
844
+
845
+ if (el.navAction == Calendar.NAV_TODAY){
846
+ date.setDateOnly(new Date());
847
+ }
848
+
849
+ var year = date.getFullYear();
850
+ var mon = date.getMonth();
851
+
852
+ function setMonth(m) {
853
+ var day = date.getDate();
854
+ var max = date.getMonthDays(m);
855
+ if (day > max) date.setDate(max);
856
+ date.setMonth(m);
857
+ }
858
+
859
+ switch (el.navAction) {
860
+
861
+ // Previous Year
862
+ case Calendar.NAV_PREVIOUS_YEAR:
863
+ if (year > calendar.minYear)
864
+ date.__setFullYear(year - 1);
865
+ break;
866
+
867
+ // Previous Month
868
+ case Calendar.NAV_PREVIOUS_MONTH:
869
+ if (mon > 0) {
870
+ setMonth(mon - 1);
871
+ }
872
+ else if (year-- > calendar.minYear) {
873
+ date.__setFullYear(year);
874
+ setMonth(11);
875
+ }
876
+ break;
877
+
878
+ // Today
879
+ case Calendar.NAV_TODAY:
880
+ break;
881
+
882
+ // Next Month
883
+ case Calendar.NAV_NEXT_MONTH:
884
+ if (mon < 11) {
885
+ setMonth(mon + 1);
886
+ }else if (year < calendar.maxYear) {
887
+ date.__setFullYear(year + 1);
888
+ setMonth(0);
889
+ }
890
+ break;
891
+
892
+ // Next Year
893
+ case Calendar.NAV_NEXT_YEAR:
894
+ if (year < calendar.maxYear){
895
+ date.__setFullYear(year + 1);
896
+ }
897
+ break;
898
+ }
899
+
900
+ if (!date.equalsTo(calendar.dateOrDateBackedUp())) {
901
+ calendar.updateIfDateDifferent(date);
902
+ isNewDate = true;
903
+ } // else if (el.navAction == 0) {
904
+ // isNewDate = (calendar.shouldClose = true);
905
+ // } // Hm, what did I mean with this code?
906
+ }
907
+
908
+ if (isNewDate && event) {
909
+ Calendar.selectHandler(calendar);
910
+ }
911
+
912
+ if (calendar.shouldClose && event) {
913
+ Calendar.closeHandler(calendar);
914
+ }
915
+
916
+ Event.stopObserving(document, 'mouseup', Calendar.handleMouseUpEvent);
917
+ return Event.stop(event);
918
+ }
919
+
920
+ Calendar.selectHandler = function(calendar){
921
+
922
+ // Update dateField value
923
+ calendar.updateOuterField();
924
+
925
+
926
+ // Call the close handler, if necessary
927
+ if (calendar.shouldClose) {
928
+ Calendar.closeHandler(calendar);
929
+ }
930
+ }
931
+
932
+ Calendar.closeHandler = function(calendar){
933
+ calendar.hide();
934
+ calendar.shouldClose = false;
935
+ }
936
+
937
+
938
+
939
+ // global object that remembers the calendar
940
+ window._popupCalendar = null;
941
+
942
+
943
+ //==============================================================================
944
+ //
945
+ // Date Object Patches
946
+ //
947
+ // This is pretty much untouched from the original. I really would like to get
948
+ // rid of these patches if at all possible and find a cleaner way of
949
+ // accomplishing the same things. It's a shame Prototype doesn't extend Date at
950
+ // all.
951
+ //
952
+ //==============================================================================
953
+
954
+ Date.DAYS_IN_MONTH = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
955
+ Date.SECOND = 1000 /* milliseconds */
956
+ Date.MINUTE = 60 * Date.SECOND
957
+ Date.HOUR = 60 * Date.MINUTE
958
+ Date.DAY = 24 * Date.HOUR
959
+ Date.WEEK = 7 * Date.DAY
960
+
961
+ // Parses Date
962
+ Date.parseDate = function(str, fmt) {
963
+ if (str){
964
+ str = new String(str);
965
+ }else{
966
+ str = new String('');
967
+ }
968
+ str = str.strip();
969
+
970
+ var today = new Date();
971
+ var y = 0;
972
+ var m = -1;
973
+ var d = 0;
974
+ var a = str.split(/\W+/);
975
+ var b = fmt.match(/%./g);
976
+ var i = 0, j = 0;
977
+ var hr = 0;
978
+ var min = 0;
979
+
980
+ for (i = 0; i < a.length; ++i) {
981
+ if (!a[i]) continue;
982
+ switch (b[i]) {
983
+ case "%d":
984
+ case "%e":
985
+ d = parseInt(a[i], 10);
986
+ break;
987
+ case "%m":
988
+ m = parseInt(a[i], 10) - 1;
989
+ break;
990
+ case "%Y":
991
+ case "%y":
992
+ y = parseInt(a[i], 10);
993
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
994
+ break;
995
+ case "%b":
996
+ case "%B":
997
+ for (j = 0; j < 12; ++j) {
998
+ if (Calendar.MONTH_NAMES[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) {
999
+ m = j;
1000
+ break;
1001
+ }
1002
+ }
1003
+ break;
1004
+ case "%H":
1005
+ case "%I":
1006
+ case "%k":
1007
+ case "%l":
1008
+ hr = parseInt(a[i], 10);
1009
+ break;
1010
+ case "%P":
1011
+ case "%p":
1012
+ if (/pm/i.test(a[i]) && hr < 12)
1013
+ hr += 12;
1014
+ else if (/am/i.test(a[i]) && hr >= 12)
1015
+ hr -= 12;
1016
+ break;
1017
+ case "%M":
1018
+ min = parseInt(a[i], 10);
1019
+ break;
1020
+ }
1021
+ }
1022
+ if (isNaN(y)) y = today.getFullYear();
1023
+ if (isNaN(m)) m = today.getMonth();
1024
+ if (isNaN(d)) d = today.getDate();
1025
+ if (isNaN(hr)) hr = today.getHours();
1026
+ if (isNaN(min)) min = today.getMinutes();
1027
+ if (y != 0 && m != -1 && d != 0)
1028
+ return new Date(y, m, d, hr, min, 0);
1029
+ y = 0; m = -1; d = 0;
1030
+ for (i = 0; i < a.length; ++i) {
1031
+ if (a[i].search(/[a-zA-Z]+/) != -1) {
1032
+ var t = -1;
1033
+ for (j = 0; j < 12; ++j) {
1034
+ if (Calendar.MONTH_NAMES[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
1035
+ }
1036
+ if (t != -1) {
1037
+ if (m != -1) {
1038
+ d = m+1;
1039
+ }
1040
+ m = t;
1041
+ }
1042
+ } else if (parseInt(a[i], 10) <= 12 && m == -1) {
1043
+ m = a[i]-1;
1044
+ } else if (parseInt(a[i], 10) > 31 && y == 0) {
1045
+ y = parseInt(a[i], 10);
1046
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
1047
+ } else if (d == 0) {
1048
+ d = a[i];
1049
+ }
1050
+ }
1051
+ if (y == 0)
1052
+ y = today.getFullYear();
1053
+ if (m != -1 && d != 0)
1054
+ return new Date(y, m, d, hr, min, 0);
1055
+ return today;
1056
+ };
1057
+
1058
+ // Returns the number of days in the current month
1059
+ Date.prototype.getMonthDays = function(month) {
1060
+ var year = this.getFullYear()
1061
+ if (typeof month == "undefined")
1062
+ month = this.getMonth()
1063
+ if (((0 == (year % 4)) && ( (0 != (year % 100)) || (0 == (year % 400)))) && month == 1)
1064
+ return 29
1065
+ else
1066
+ return Date.DAYS_IN_MONTH[month]
1067
+ };
1068
+
1069
+ // Returns the number of day in the year
1070
+ Date.prototype.getDayOfYear = function() {
1071
+ var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
1072
+ var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
1073
+ var time = now - then;
1074
+ return Math.floor(time / Date.DAY);
1075
+ };
1076
+
1077
+ /** Returns the number of the week in year, as defined in ISO 8601. */
1078
+ Date.prototype.getWeekNumber = function() {
1079
+ var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
1080
+ var DoW = d.getDay();
1081
+ d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
1082
+ var ms = d.valueOf(); // GMT
1083
+ d.setMonth(0);
1084
+ d.setDate(4); // Thu in Week 1
1085
+ return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
1086
+ };
1087
+
1088
+ /** Checks date and time equality */
1089
+ Date.prototype.equalsTo = function(date) {
1090
+ return ((this.getFullYear() == date.getFullYear()) &&
1091
+ (this.getMonth() == date.getMonth()) &&
1092
+ (this.getDate() == date.getDate()) &&
1093
+ (this.getHours() == date.getHours()) &&
1094
+ (this.getMinutes() == date.getMinutes()));
1095
+ };
1096
+
1097
+ /** Set only the year, month, date parts (keep existing time) */
1098
+ Date.prototype.setDateOnly = function(date) {
1099
+ var tmp = new Date(date);
1100
+ this.setDate(1);
1101
+ this.__setFullYear(tmp.getFullYear());
1102
+ this.setMonth(tmp.getMonth());
1103
+ this.setDate(tmp.getDate());
1104
+ };
1105
+
1106
+ /** Prints the date in a string according to the given format. */
1107
+ Date.prototype.print = function (str) {
1108
+ var m = this.getMonth();
1109
+ var d = this.getDate();
1110
+ var y = this.getFullYear();
1111
+ var wn = this.getWeekNumber();
1112
+ var w = this.getDay();
1113
+ var s = {};
1114
+ var hr = this.getHours();
1115
+ var pm = (hr >= 12);
1116
+ var ir = (pm) ? (hr - 12) : hr;
1117
+ var dy = this.getDayOfYear();
1118
+ if (ir == 0)
1119
+ ir = 12;
1120
+ var min = this.getMinutes();
1121
+ var sec = this.getSeconds();
1122
+ s["%a"] = Calendar.SHORT_DAY_NAMES[w]; // abbreviated weekday name [FIXME: I18N]
1123
+ s["%A"] = Calendar.DAY_NAMES[w]; // full weekday name
1124
+ s["%b"] = Calendar.SHORT_MONTH_NAMES[m]; // abbreviated month name [FIXME: I18N]
1125
+ s["%B"] = Calendar.MONTH_NAMES[m]; // full month name
1126
+ // FIXME: %c : preferred date and time representation for the current locale
1127
+ s["%C"] = 1 + Math.floor(y / 100); // the century number
1128
+ s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
1129
+ s["%e"] = d; // the day of the month (range 1 to 31)
1130
+ // FIXME: %D : american date style: %m/%d/%y
1131
+ // FIXME: %E, %F, %G, %g, %h (man strftime)
1132
+ s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
1133
+ s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
1134
+ s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
1135
+ s["%k"] = hr; // hour, range 0 to 23 (24h format)
1136
+ s["%l"] = ir; // hour, range 1 to 12 (12h format)
1137
+ s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
1138
+ s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
1139
+ s["%n"] = "\n"; // a newline character
1140
+ s["%p"] = pm ? "PM" : "AM";
1141
+ s["%P"] = pm ? "pm" : "am";
1142
+ // FIXME: %r : the time in am/pm notation %I:%M:%S %p
1143
+ // FIXME: %R : the time in 24-hour notation %H:%M
1144
+ s["%s"] = Math.floor(this.getTime() / 1000);
1145
+ s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
1146
+ s["%t"] = "\t"; // a tab character
1147
+ // FIXME: %T : the time in 24-hour notation (%H:%M:%S)
1148
+ s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
1149
+ s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
1150
+ s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
1151
+ // FIXME: %x : preferred date representation for the current locale without the time
1152
+ // FIXME: %X : preferred time representation for the current locale without the date
1153
+ s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
1154
+ s["%Y"] = y; // year with the century
1155
+ s["%%"] = "%"; // a literal '%' character
1156
+
1157
+ return str.gsub(/%./, function(match) { return s[match] || match });
1158
+ };
1159
+
1160
+
1161
+ Date.prototype.__setFullYear = function(y) {
1162
+ var d = new Date(this);
1163
+ d.setFullYear(y);
1164
+ if (d.getMonth() != this.getMonth())
1165
+ this.setDate(28);
1166
+ this.setFullYear(y);
1167
+ };
1168
+