mongoid_wice_grid 4.0.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.
- data/.gitignore +9 -0
- data/CHANGELOG +409 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +140 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +1188 -0
- data/Rakefile +40 -0
- data/SAVED_QUERIES_HOWTO.rdoc +123 -0
- data/VERSION +1 -0
- data/lib/filter_conditions_generators.rb +126 -0
- data/lib/generators/wice_grid/templates/calendarview.css +107 -0
- data/lib/generators/wice_grid/templates/calendarview.js +1168 -0
- data/lib/generators/wice_grid/templates/icons/arrow_down.gif +0 -0
- data/lib/generators/wice_grid/templates/icons/arrow_up.gif +0 -0
- data/lib/generators/wice_grid/templates/icons/calendar_view_month.png +0 -0
- data/lib/generators/wice_grid/templates/icons/delete.png +0 -0
- data/lib/generators/wice_grid/templates/icons/expand.png +0 -0
- data/lib/generators/wice_grid/templates/icons/page_white_excel.png +0 -0
- data/lib/generators/wice_grid/templates/icons/page_white_find.png +0 -0
- data/lib/generators/wice_grid/templates/icons/table.png +0 -0
- data/lib/generators/wice_grid/templates/icons/table_refresh.png +0 -0
- data/lib/generators/wice_grid/templates/icons/tick_all.png +0 -0
- data/lib/generators/wice_grid/templates/icons/untick_all.png +0 -0
- data/lib/generators/wice_grid/templates/wice_grid.css +173 -0
- data/lib/generators/wice_grid/templates/wice_grid.yml +269 -0
- data/lib/generators/wice_grid/templates/wice_grid_config.rb +215 -0
- data/lib/generators/wice_grid/templates/wice_grid_jquery.js +161 -0
- data/lib/generators/wice_grid/templates/wice_grid_prototype.js +153 -0
- data/lib/generators/wice_grid/wice_grid_assets_jquery_generator.rb +32 -0
- data/lib/generators/wice_grid/wice_grid_assets_prototype_generator.rb +34 -0
- data/lib/grid_output_buffer.rb +52 -0
- data/lib/grid_renderer.rb +547 -0
- data/lib/helpers/js_calendar_helpers.rb +183 -0
- data/lib/helpers/wice_grid_misc_view_helpers.rb +113 -0
- data/lib/helpers/wice_grid_serialized_queries_view_helpers.rb +82 -0
- data/lib/helpers/wice_grid_view_helpers.rb +761 -0
- data/lib/js_adaptors/jquery_adaptor.rb +145 -0
- data/lib/js_adaptors/js_adaptor.rb +12 -0
- data/lib/js_adaptors/prototype_adaptor.rb +168 -0
- data/lib/mongoid_field.rb +50 -0
- data/lib/tasks/wice_grid_tasks.rake +28 -0
- data/lib/view_columns.rb +464 -0
- data/lib/views/create.rjs +13 -0
- data/lib/views/delete.rjs +12 -0
- data/lib/wice_grid.rb +521 -0
- data/lib/wice_grid_controller.rb +165 -0
- data/lib/wice_grid_core_ext.rb +179 -0
- data/lib/wice_grid_misc.rb +99 -0
- data/lib/wice_grid_serialized_queries_controller.rb +77 -0
- data/lib/wice_grid_serialized_query.rb +14 -0
- data/lib/wice_grid_spreadsheet.rb +33 -0
- data/test/.gitignore +2 -0
- data/test/blueprint.rb +17 -0
- data/test/database.yml +21 -0
- data/test/public/javascripts/jquery-1.4.2.min.js +154 -0
- data/test/public/javascripts/wice_grid.js +163 -0
- data/test/rails_mongoid_test.rb +104 -0
- data/test/rails_test_app.rb +71 -0
- data/test/require_gems.rb +19 -0
- data/test/schema.rb +33 -0
- data/test/spec_helper.rb +22 -0
- data/test/test_helper.rb +89 -0
- data/test/views/projects_and_people_grid.html.erb +12 -0
- data/test/views/projects_and_people_grid_invalid.html.erb +12 -0
- data/test/views/simple_projects_grid.html.erb +9 -0
- data/test/wice_grid_core_ext_test.rb +183 -0
- data/test/wice_grid_functional_test.rb +68 -0
- data/test/wice_grid_initializer.rb +215 -0
- data/test/wice_grid_misc_test.rb +41 -0
- data/test/wice_grid_test.rb +42 -0
- data/test/wice_grid_view_helper_test.rb +12 -0
- metadata +150 -0
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
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 = "mongoid_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 = "aleksandr.furmanov@gmail.com"
|
33
|
+
gem.homepage = "https://github.com/afurmanov/wice_grid"
|
34
|
+
gem.authors = ["Yuri Leikind", "Aleksandr Furmanov"]
|
35
|
+
end
|
36
|
+
Jeweler::GemcutterTasks.new
|
37
|
+
rescue LoadError
|
38
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
39
|
+
end
|
40
|
+
|
@@ -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
|
+
4.0.0
|
@@ -0,0 +1,126 @@
|
|
1
|
+
module Wice
|
2
|
+
class FilterConditionsGenerator #:nodoc:
|
3
|
+
|
4
|
+
cattr_accessor :handled_type
|
5
|
+
@@handled_type = HashWithIndifferentAccess.new
|
6
|
+
|
7
|
+
def initialize(field, criteria) #:nodoc:
|
8
|
+
@field = field
|
9
|
+
@criteria = criteria
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate_conditions(opts)
|
13
|
+
raise "must be implemented"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# class FilterConditionsGeneratorCustomFilter < FilterConditionsGenerator #:nodoc:
|
18
|
+
|
19
|
+
# def generate_conditions(opts) #:nodoc:
|
20
|
+
# if opts.empty?
|
21
|
+
# Wice.log "empty parameters for the grid custom filter"
|
22
|
+
# return false
|
23
|
+
# end
|
24
|
+
# opts = (opts.kind_of?(Array) && opts.size == 1) ? opts[0] : opts
|
25
|
+
|
26
|
+
# if opts.kind_of?(Array)
|
27
|
+
# opts_with_special_values, normal_opts = opts.partition{|v| ::Wice::GridTools.special_value(v)}
|
28
|
+
|
29
|
+
# conditions_ar = if normal_opts.size > 0
|
30
|
+
# [" #{@field.alias_or_table_name(table_alias)}.#{@field.name} IN ( " + (['?'] * normal_opts.size).join(', ') + ' )'] + normal_opts
|
31
|
+
# else
|
32
|
+
# []
|
33
|
+
# end
|
34
|
+
|
35
|
+
# if opts_with_special_values.size > 0
|
36
|
+
# special_conditions = opts_with_special_values.collect{|v| " #{@field.alias_or_table_name(table_alias)}.#{@field.name} is " + v}.join(' or ')
|
37
|
+
# if conditions_ar.size > 0
|
38
|
+
# conditions_ar[0] = " (#{conditions_ar[0]} or #{special_conditions} ) "
|
39
|
+
# else
|
40
|
+
# conditions_ar = " ( #{special_conditions} ) "
|
41
|
+
# end
|
42
|
+
# end
|
43
|
+
# conditions_ar
|
44
|
+
# else
|
45
|
+
# if ::Wice::GridTools.special_value(opts)
|
46
|
+
# " #{@field.alias_or_table_name(table_alias)}.#{@field.name} is " + opts
|
47
|
+
# else
|
48
|
+
# [" #{@field.alias_or_table_name(table_alias)}.#{@field.name} = ?", opts]
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
|
53
|
+
# end
|
54
|
+
|
55
|
+
class FilterConditionsGeneratorBoolean < FilterConditionsGenerator #:nodoc:
|
56
|
+
@@handled_type[Boolean] = self
|
57
|
+
|
58
|
+
def generate_conditions(opts) #:nodoc:
|
59
|
+
unless (opts.kind_of?(Array) && opts.size == 1 && ['f', 't'].include?(opts[0]))
|
60
|
+
Wice.log "invalid parameters for the grid boolean filter - must be an one item array: #{opts.inspect}"
|
61
|
+
return false
|
62
|
+
end
|
63
|
+
@criteria.where(@field.name.to_s => opts[0] == 't' ? true : false)
|
64
|
+
return true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class FilterConditionsGeneratorString < FilterConditionsGenerator #:nodoc:
|
69
|
+
@@handled_type[String] = self
|
70
|
+
|
71
|
+
def generate_conditions(opts) #:nodoc:
|
72
|
+
negation = nil
|
73
|
+
if opts.kind_of? String
|
74
|
+
string_fragment = opts
|
75
|
+
elsif (opts.kind_of? Hash) && opts.has_key?(:v)
|
76
|
+
string_fragment = opts[:v]
|
77
|
+
#negation = opts[:n] == '1' ? 'NOT' : ''
|
78
|
+
else
|
79
|
+
Wice.log "invalid parameters for the grid string filter - must be a string: #{opts.inspect} or a Hash with keys :v and :n"
|
80
|
+
return false
|
81
|
+
end
|
82
|
+
if string_fragment.empty?
|
83
|
+
Wice.log "invalid parameters for the grid string filter - empty string"
|
84
|
+
return false
|
85
|
+
end
|
86
|
+
@criteria.where(@field.name.to_s => /#{string_fragment}/)
|
87
|
+
return true
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
class FilterConditionsGeneratorInteger < FilterConditionsGenerator #:nodoc:
|
93
|
+
@@handled_type[Integer] = self
|
94
|
+
# @@handled_type[Float] = self
|
95
|
+
# @@handled_type[BigDecimal] = self
|
96
|
+
|
97
|
+
def generate_conditions(opts) #:nodoc:
|
98
|
+
unless opts.kind_of? Hash
|
99
|
+
Wice.log "invalid parameters for the grid integer filter - must be a hash"
|
100
|
+
return false
|
101
|
+
end
|
102
|
+
|
103
|
+
if !opts[:fr] || !(opts[:fr] =~ /\d/) || !opts[:to] || !(opts[:to] =~ /\d/)
|
104
|
+
Wice.log "invalid parameters for the grid integer filter - either range limits are not supplied or they are not numeric"
|
105
|
+
return false
|
106
|
+
end
|
107
|
+
|
108
|
+
@criteria.where(@field.name.to_sym.gt => opts[:fr].to_i)
|
109
|
+
@criteria.where(@field.name.to_sym.lt => opts[:to].to_i)
|
110
|
+
|
111
|
+
return true
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
class FilterConditionsGeneratorDate < FilterConditionsGenerator #:nodoc:
|
116
|
+
@@handled_type[Date] = self
|
117
|
+
@@handled_type[DateTime] = self
|
118
|
+
@@handled_type[Time] = self
|
119
|
+
|
120
|
+
def generate_conditions(opts) #:nodoc:
|
121
|
+
@criteria.where(@field.name.to_sym.gt => opts[:fr]) if opts[:fr]
|
122
|
+
@criteria.where(@field.name.to_sym.lt => opts[:to]) if opts[:to]
|
123
|
+
opts[:fr] || opts[:to]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -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, '«', 1, Calendar.NAV_PREVIOUS_YEAR);
|
173
|
+
this._drawButtonCell(row, '‹', 1, Calendar.NAV_PREVIOUS_MONTH);
|
174
|
+
this._drawButtonCell(row, Calendar.getMessageFor('today'), 3, Calendar.NAV_TODAY);
|
175
|
+
this._drawButtonCell(row, '›', 1, Calendar.NAV_NEXT_MONTH);
|
176
|
+
this._drawButtonCell(row, '»', 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
|
+
|