Commit c2e00580b00093df5558ce50ed30500fb8113fd2
Exists in
master
and in
23 other branches
Merge commit 'refs/merge-requests/231' of git://gitorious.org/noosfero/noosfero …
…into merge-requests/231
Showing
52 changed files
with
3197 additions
and
20 deletions
Show diff stats
app/helpers/application_helper.rb
... | ... | @@ -265,9 +265,9 @@ module ApplicationHelper |
265 | 265 | |
266 | 266 | VIEW_EXTENSIONS = %w[.rhtml .html.erb] |
267 | 267 | |
268 | - def partial_for_class_in_view_path(klass, view_path) | |
268 | + def partial_for_class_in_view_path(klass, view_path, suffix = nil) | |
269 | 269 | return nil if klass.nil? |
270 | - name = klass.name.underscore | |
270 | + name = [klass.name.underscore, suffix].compact.map(&:to_s).join('_') | |
271 | 271 | |
272 | 272 | search_name = String.new(name) |
273 | 273 | if search_name.include?("/") |
... | ... | @@ -285,28 +285,17 @@ module ApplicationHelper |
285 | 285 | partial_for_class_in_view_path(klass.superclass, view_path) |
286 | 286 | end |
287 | 287 | |
288 | - def partial_for_class(klass) | |
288 | + def partial_for_class(klass, suffix=nil) | |
289 | 289 | raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil? |
290 | 290 | name = klass.name.underscore |
291 | 291 | @controller.view_paths.each do |view_path| |
292 | - partial = partial_for_class_in_view_path(klass, view_path) | |
292 | + partial = partial_for_class_in_view_path(klass, view_path, suffix) | |
293 | 293 | return partial if partial |
294 | 294 | end |
295 | 295 | |
296 | 296 | raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' |
297 | 297 | end |
298 | 298 | |
299 | - def partial_for_task_class(klass, action) | |
300 | - raise ArgumentError, 'No partial for object. Is there a partial for any class in the inheritance hierarchy?' if klass.nil? | |
301 | - | |
302 | - name = "#{klass.name.underscore}_#{action.to_s}" | |
303 | - VIEW_EXTENSIONS.each do |ext| | |
304 | - return name if File.exists?(File.join(RAILS_ROOT, 'app', 'views', params[:controller], '_'+name+ext)) | |
305 | - end | |
306 | - | |
307 | - partial_for_task_class(klass.superclass, action) | |
308 | - end | |
309 | - | |
310 | 299 | def view_for_profile_actions(klass) |
311 | 300 | raise ArgumentError, 'No profile actions view for this class.' if klass.nil? |
312 | 301 | ... | ... |
app/helpers/forms_helper.rb
... | ... | @@ -123,6 +123,119 @@ module FormsHelper |
123 | 123 | options_for_select.join("\n") |
124 | 124 | end |
125 | 125 | |
126 | + def date_field(name, value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {}) | |
127 | + datepicker_options[:disabled] ||= false | |
128 | + datepicker_options[:alt_field] ||= '' | |
129 | + datepicker_options[:alt_format] ||= '' | |
130 | + datepicker_options[:append_text] ||= '' | |
131 | + datepicker_options[:auto_size] ||= false | |
132 | + datepicker_options[:button_image] ||= '' | |
133 | + datepicker_options[:button_image_only] ||= false | |
134 | + datepicker_options[:button_text] ||= '...' | |
135 | + datepicker_options[:calculate_week] ||= 'jQuery.datepicker.iso8601Week' | |
136 | + datepicker_options[:change_month] ||= false | |
137 | + datepicker_options[:change_year] ||= false | |
138 | + datepicker_options[:close_text] ||= _('Done') | |
139 | + datepicker_options[:constrain_input] ||= true | |
140 | + datepicker_options[:current_text] ||= _('Today') | |
141 | + datepicker_options[:date_format] ||= 'mm/dd/yy' | |
142 | + datepicker_options[:day_names] ||= [_('Sunday'), _('Monday'), _('Tuesday'), _('Wednesday'), _('Thursday'), _('Friday'), _('Saturday')] | |
143 | + datepicker_options[:day_names_min] ||= [_('Su'), _('Mo'), _('Tu'), _('We'), _('Th'), _('Fr'), _('Sa')] | |
144 | + datepicker_options[:day_names_short] ||= [_('Sun'), _('Mon'), _('Tue'), _('Wed'), _('Thu'), _('Fri'), _('Sat')] | |
145 | + datepicker_options[:default_date] ||= nil | |
146 | + datepicker_options[:duration] ||= 'normal' | |
147 | + datepicker_options[:first_day] ||= 0 | |
148 | + datepicker_options[:goto_current] ||= false | |
149 | + datepicker_options[:hide_if_no_prev_next] ||= false | |
150 | + datepicker_options[:is_rtl] ||= false | |
151 | + datepicker_options[:max_date] ||= nil | |
152 | + datepicker_options[:min_date] ||= nil | |
153 | + datepicker_options[:month_names] ||= [_('January'), _('February'), _('March'), _('April'), _('May'), _('June'), _('July'), _('August'), _('September'), _('October'), _('November'), _('December')] | |
154 | + datepicker_options[:month_names_short] ||= [_('Jan'), _('Feb'), _('Mar'), _('Apr'), _('May'), _('Jun'), _('Jul'), _('Aug'), _('Sep'), _('Oct'), _('Nov'), _('Dec')] | |
155 | + datepicker_options[:navigation_as_date_format] ||= false | |
156 | + datepicker_options[:next_text] ||= _('Next') | |
157 | + datepicker_options[:number_of_months] ||= 1 | |
158 | + datepicker_options[:prev_text] ||= _('Prev') | |
159 | + datepicker_options[:select_other_months] ||= false | |
160 | + datepicker_options[:short_year_cutoff] ||= '+10' | |
161 | + datepicker_options[:show_button_panel] ||= false | |
162 | + datepicker_options[:show_current_at_pos] ||= 0 | |
163 | + datepicker_options[:show_month_after_year] ||= false | |
164 | + datepicker_options[:show_on] ||= 'focus' | |
165 | + datepicker_options[:show_options] ||= {} | |
166 | + datepicker_options[:show_other_months] ||= false | |
167 | + datepicker_options[:show_week] ||= false | |
168 | + datepicker_options[:step_months] ||= 1 | |
169 | + datepicker_options[:week_header] ||= _('Wk') | |
170 | + datepicker_options[:year_range] ||= 'c-10:c+10' | |
171 | + datepicker_options[:year_suffix] ||= '' | |
172 | + | |
173 | + element_id = html_options[:id] || 'datepicker-date' | |
174 | + value = value.strftime(format) if value.present? | |
175 | + method = datepicker_options[:time] ? 'datetimepicker' : 'datepicker' | |
176 | + result = text_field_tag(name, value, html_options) | |
177 | + result += | |
178 | + " | |
179 | + <script type='text/javascript'> | |
180 | + jQuery('##{element_id}').#{method}({ | |
181 | + disabled: #{datepicker_options[:disabled].to_json}, | |
182 | + altField: #{datepicker_options[:alt_field].to_json}, | |
183 | + altFormat: #{datepicker_options[:alt_format].to_json}, | |
184 | + appendText: #{datepicker_options[:append_text].to_json}, | |
185 | + autoSize: #{datepicker_options[:auto_size].to_json}, | |
186 | + buttonImage: #{datepicker_options[:button_image].to_json}, | |
187 | + buttonImageOnly: #{datepicker_options[:button_image_only].to_json}, | |
188 | + buttonText: #{datepicker_options[:button_text].to_json}, | |
189 | + calculateWeek: #{datepicker_options[:calculate_week].to_json}, | |
190 | + changeMonth: #{datepicker_options[:change_month].to_json}, | |
191 | + changeYear: #{datepicker_options[:change_year].to_json}, | |
192 | + closeText: #{datepicker_options[:close_text].to_json}, | |
193 | + constrainInput: #{datepicker_options[:constrain_input].to_json}, | |
194 | + currentText: #{datepicker_options[:current_text].to_json}, | |
195 | + dateFormat: #{datepicker_options[:date_format].to_json}, | |
196 | + dayNames: #{datepicker_options[:day_names].to_json}, | |
197 | + dayNamesMin: #{datepicker_options[:day_names_min].to_json}, | |
198 | + dayNamesShort: #{datepicker_options[:day_names_short].to_json}, | |
199 | + defaultDate: #{datepicker_options[:default_date].to_json}, | |
200 | + duration: #{datepicker_options[:duration].to_json}, | |
201 | + firstDay: #{datepicker_options[:first_day].to_json}, | |
202 | + gotoCurrent: #{datepicker_options[:goto_current].to_json}, | |
203 | + hideIfNoPrevNext: #{datepicker_options[:hide_if_no_prev_next].to_json}, | |
204 | + isRTL: #{datepicker_options[:is_rtl].to_json}, | |
205 | + maxDate: #{datepicker_options[:max_date].to_json}, | |
206 | + minDate: #{datepicker_options[:min_date].to_json}, | |
207 | + monthNames: #{datepicker_options[:month_names].to_json}, | |
208 | + monthNamesShort: #{datepicker_options[:month_names_short].to_json}, | |
209 | + navigationAsDateFormat: #{datepicker_options[:navigation_as_date_format].to_json}, | |
210 | + nextText: #{datepicker_options[:next_text].to_json}, | |
211 | + numberOfMonths: #{datepicker_options[:number_of_months].to_json}, | |
212 | + prevText: #{datepicker_options[:prev_text].to_json}, | |
213 | + selectOtherMonths: #{datepicker_options[:select_other_months].to_json}, | |
214 | + shortYearCutoff: #{datepicker_options[:short_year_cutoff].to_json}, | |
215 | + showButtonPanel: #{datepicker_options[:show_button_panel].to_json}, | |
216 | + showCurrentAtPos: #{datepicker_options[:show_current_at_pos].to_json}, | |
217 | + showMonthAfterYear: #{datepicker_options[:show_month_after_year].to_json}, | |
218 | + showOn: #{datepicker_options[:show_on].to_json}, | |
219 | + showOptions: #{datepicker_options[:show_options].to_json}, | |
220 | + showOtherMonths: #{datepicker_options[:show_other_months].to_json}, | |
221 | + showWeek: #{datepicker_options[:show_week].to_json}, | |
222 | + stepMonths: #{datepicker_options[:step_months].to_json}, | |
223 | + weekHeader: #{datepicker_options[:week_header].to_json}, | |
224 | + yearRange: #{datepicker_options[:year_range].to_json}, | |
225 | + yearSuffix: #{datepicker_options[:year_suffix].to_json} | |
226 | + }) | |
227 | + </script> | |
228 | + " | |
229 | + result | |
230 | + end | |
231 | + | |
232 | + def date_range_field(from_name, to_name, from_value, to_value, format = '%Y-%m-%d', datepicker_options = {}, html_options = {}) | |
233 | + from_id = html_options[:from_id] || 'datepicker-from-date' | |
234 | + to_id = html_options[:to_id] || 'datepicker-to-date' | |
235 | + return _('From') +' '+ date_field(from_name, from_value, format, datepicker_options, html_options.merge({:id => from_id})) + | |
236 | + ' ' + _('until') +' '+ date_field(to_name, to_value, format, datepicker_options, html_options.merge({:id => to_id})) | |
237 | + end | |
238 | + | |
126 | 239 | protected |
127 | 240 | def self.next_id_number |
128 | 241 | if defined? @@id_num | ... | ... |
app/models/task.rb
... | ... | @@ -31,7 +31,7 @@ class Task < ActiveRecord::Base |
31 | 31 | end |
32 | 32 | end |
33 | 33 | |
34 | - belongs_to :requestor, :class_name => 'Person', :foreign_key => :requestor_id | |
34 | + belongs_to :requestor, :class_name => 'Profile', :foreign_key => :requestor_id | |
35 | 35 | belongs_to :target, :foreign_key => :target_id, :polymorphic => true |
36 | 36 | |
37 | 37 | validates_uniqueness_of :code, :on => :create | ... | ... |
app/views/layouts/_javascript.rhtml
... | ... | @@ -2,7 +2,8 @@ |
2 | 2 | 'jquery.noconflict.js', 'jquery.cycle.all.min.js', 'thickbox.js', 'lightbox', 'colorbox', |
3 | 3 | 'jquery-ui-1.8.2.custom.min', 'jquery.scrollTo', 'jquery.form.js', 'jquery-validation/jquery.validate', |
4 | 4 | 'jquery.cookie', 'jquery.ba-bbq.min.js', 'reflection', 'jquery.tokeninput', |
5 | -'add-and-join', 'report-abuse', 'catalog', 'manage-products', :cache => 'cache-general' %> | |
5 | +'add-and-join', 'report-abuse', 'catalog', 'manage-products', | |
6 | +'jquery-ui-timepicker-addon', :cache => 'cache-general' %> | |
6 | 7 | |
7 | 8 | <% language = FastGettext.locale %> |
8 | 9 | <% %w{messages methods}.each do |type| %> | ... | ... |
app/views/tasks/_task.rhtml
... | ... | @@ -50,13 +50,13 @@ |
50 | 50 | <% fields_for "tasks[#{task.id}][task]", task do |f| %> |
51 | 51 | <% if task.accept_details %> |
52 | 52 | <div id="on-accept-information-<%=task.id%>" style="display: none"> |
53 | - <%= render :partial => partial_for_task_class(task.class, :accept_details), :locals => {:task => task, :f => f} %> | |
53 | + <%= render :partial => partial_for_class(task.class, :accept_details), :locals => {:task => task, :f => f} %> | |
54 | 54 | </div> |
55 | 55 | <% end %> |
56 | 56 | |
57 | 57 | <% if task.reject_details %> |
58 | 58 | <div id="on-reject-information-<%=task.id%>" style="display: none"> |
59 | - <%= render :partial => partial_for_task_class(task.class, :reject_details), :locals => {:task => task, :f => f} %> | |
59 | + <%= render :partial => partial_for_class(task.class, :reject_details), :locals => {:task => task, :f => f} %> | |
60 | 60 | </div> |
61 | 61 | <% end %> |
62 | 62 | <% end %> | ... | ... |
plugins/custom_forms/controllers/custom_forms_plugin_myprofile_controller.rb
0 → 100644
... | ... | @@ -0,0 +1,145 @@ |
1 | +class CustomFormsPluginMyprofileController < MyProfileController | |
2 | + def index | |
3 | + @forms = CustomFormsPlugin::Form.from(profile) | |
4 | + end | |
5 | + | |
6 | + def create | |
7 | + @form = CustomFormsPlugin::Form.new(:profile => profile) | |
8 | + @fields = [] | |
9 | + @empty_field = CustomFormsPlugin::Field.new | |
10 | + if request.post? | |
11 | + begin | |
12 | + @form.update_attributes!(params[:form]) | |
13 | + params[:fields] = format_kind(params[:fields]) | |
14 | + params[:fields] = format_choices(params[:fields]) | |
15 | + params[:fields] = set_form_id(params[:fields], @form.id) | |
16 | + create_fields(new_fields(params)) | |
17 | + session['notice'] = _('Form created') | |
18 | + redirect_to :action => 'index' | |
19 | + rescue Exception => exception | |
20 | + logger.error(exception.to_s) | |
21 | + session['notice'] = _('Form could not be created') | |
22 | + end | |
23 | + end | |
24 | + end | |
25 | + | |
26 | + def edit | |
27 | + @form = CustomFormsPlugin::Form.find(params[:id]) | |
28 | + @fields = @form.fields | |
29 | + @empty_field = CustomFormsPlugin::TextField.new | |
30 | + if request.post? | |
31 | + begin | |
32 | + @form.update_attributes!(params[:form]) | |
33 | + params[:fields] = format_kind(params[:fields]) | |
34 | + params[:fields] = format_choices(params[:fields]) | |
35 | + remove_fields(params, @form) | |
36 | + create_fields(new_fields(params)) | |
37 | + update_fields(edited_fields(params)) | |
38 | + session['notice'] = _('Form updated') | |
39 | + redirect_to :action => 'index' | |
40 | + rescue Exception => exception | |
41 | + logger.error(exception.to_s) | |
42 | + session['notice'] = _('Form could not be updated') | |
43 | + end | |
44 | + end | |
45 | + end | |
46 | + | |
47 | + def remove | |
48 | + @form = CustomFormsPlugin::Form.find(params[:id]) | |
49 | + begin | |
50 | + @form.destroy | |
51 | + session[:notice] = _('Form removed') | |
52 | + rescue | |
53 | + session[:notice] = _('Form could not be removed') | |
54 | + end | |
55 | + redirect_to :action => 'index' | |
56 | + end | |
57 | + | |
58 | + def submissions | |
59 | + @form = CustomFormsPlugin::Form.find(params[:id]) | |
60 | + @submissions = @form.submissions | |
61 | + end | |
62 | + | |
63 | + def show_submission | |
64 | + @submission = CustomFormsPlugin::Submission.find(params[:id]) | |
65 | + @form = @submission.form | |
66 | + end | |
67 | + | |
68 | + private | |
69 | + | |
70 | + def new_fields(params) | |
71 | + result = params[:fields].map {|id, hash| hash.has_key?(:real_id) ? nil : hash}.compact | |
72 | + result.delete_if {|field| field[:name].blank?} | |
73 | + result | |
74 | + end | |
75 | + | |
76 | + def edited_fields(params) | |
77 | + params[:fields].map {|id, hash| hash.has_key?(:real_id) ? hash : nil}.compact | |
78 | + end | |
79 | + | |
80 | + def create_fields(fields) | |
81 | + fields.each do |field| | |
82 | + case field[:type] | |
83 | + when 'text_field' | |
84 | + CustomFormsPlugin::TextField.create!(field) | |
85 | + when 'select_field' | |
86 | + CustomFormsPlugin::SelectField.create!(field) | |
87 | + else | |
88 | + CustomFormsPlugin::Field.create!(field) | |
89 | + end | |
90 | + end | |
91 | + end | |
92 | + | |
93 | + def update_fields(fields) | |
94 | + fields.each do |field_attrs| | |
95 | + field = CustomFormsPlugin::Field.find(field_attrs.delete(:real_id)) | |
96 | + field.attributes = field_attrs | |
97 | + field.save! if field.changed? | |
98 | + end | |
99 | + end | |
100 | + | |
101 | + def format_kind(fields) | |
102 | + fields.each do |id, field| | |
103 | + next if field[:kind].blank? | |
104 | + kind = field.delete(:kind) | |
105 | + case kind | |
106 | + when 'radio' | |
107 | + field[:list] = false | |
108 | + field[:multiple] = false | |
109 | + when 'check_box' | |
110 | + field[:list] = false | |
111 | + field[:multiple] = true | |
112 | + when 'select' | |
113 | + field[:list] = true | |
114 | + field[:multiple] = false | |
115 | + when 'multiple_select' | |
116 | + field[:list] = true | |
117 | + field[:multiple] = true | |
118 | + end | |
119 | + end | |
120 | + fields | |
121 | + end | |
122 | + | |
123 | + def format_choices(fields) | |
124 | + fields.each do |id, field| | |
125 | + next if !field.has_key?(:choices) | |
126 | + field[:choices] = field[:choices].map {|key, value| value}.inject({}) do |result, choice| | |
127 | + hash = (choice[:name].blank? || choice[:value].blank?) ? {} : {choice[:name] => choice[:value]} | |
128 | + result.merge!(hash) | |
129 | + end | |
130 | + end | |
131 | + fields | |
132 | + end | |
133 | + | |
134 | + def remove_fields(params, form) | |
135 | + present_fields = params[:fields].map{|id, value| value}.collect {|field| field[:real_id]}.compact | |
136 | + form.fields.each {|field| field.destroy if !present_fields.include?(field.id.to_s) } | |
137 | + end | |
138 | + | |
139 | + def set_form_id(fields, form_id) | |
140 | + fields.each do |id, field| | |
141 | + field[:form_id] = form_id | |
142 | + end | |
143 | + fields | |
144 | + end | |
145 | +end | ... | ... |
plugins/custom_forms/controllers/custom_forms_plugin_profile_controller.rb
0 → 100644
... | ... | @@ -0,0 +1,45 @@ |
1 | +class CustomFormsPluginProfileController < ProfileController | |
2 | + | |
3 | + before_filter :has_access, :show | |
4 | + | |
5 | + def show | |
6 | + @form = CustomFormsPlugin::Form.find(params[:id]) | |
7 | + if user | |
8 | + @submission ||= CustomFormsPlugin::Submission.find_by_form_id_and_profile_id(@form.id,user.id) | |
9 | + @submission ||= CustomFormsPlugin::Submission.new(:form_id => @form.id, :profile_id => user.id) | |
10 | + else | |
11 | + @submission ||= CustomFormsPlugin::Submission.new(:form_id => @form.id) | |
12 | + end | |
13 | + if request.post? | |
14 | + begin | |
15 | + extend(CustomFormsPlugin::Helper) | |
16 | + answers = build_answers(params[:submission], @form) | |
17 | + failed_answers = answers.select {|answer| !answer.valid? } | |
18 | + if failed_answers.empty? | |
19 | + if !user | |
20 | + @submission.author_name = params[:author_name] | |
21 | + @submission.author_email = params[:author_email] | |
22 | + end | |
23 | + @submission.save! | |
24 | + answers.map {|answer| answer.submission = @submission; answer.save!} | |
25 | + else | |
26 | + @submission.valid? | |
27 | + failed_answers.each do |answer| | |
28 | + @submission.errors.add(answer.field.name.to_sym, answer.errors[answer.field.slug.to_sym]) | |
29 | + end | |
30 | + end | |
31 | + session[:notice] = _('Submission saved') | |
32 | + redirect_to :action => 'show' | |
33 | + rescue | |
34 | + session[:notice] = _('Submission could not be saved') | |
35 | + end | |
36 | + end | |
37 | + end | |
38 | + | |
39 | + private | |
40 | + | |
41 | + def has_access | |
42 | + form = CustomFormsPlugin::Form.find(params[:id]) | |
43 | + render_access_denied if !form.accessible_to(user) | |
44 | + end | |
45 | +end | ... | ... |
plugins/custom_forms/db/migrate/20120727162444_create_custom_forms_plugin_forms.rb
0 → 100644
... | ... | @@ -0,0 +1,20 @@ |
1 | +class CreateCustomFormsPluginForms < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + create_table :custom_forms_plugin_forms do |t| | |
4 | + t.string :name | |
5 | + t.string :slug | |
6 | + t.text :description | |
7 | + t.references :profile | |
8 | + t.datetime :begining | |
9 | + t.datetime :ending | |
10 | + t.boolean :report_submissions, :default => false | |
11 | + t.boolean :on_membership, :default => false | |
12 | + t.string :access | |
13 | + t.timestamps | |
14 | + end | |
15 | + end | |
16 | + | |
17 | + def self.down | |
18 | + drop_table :custom_forms_plugin_forms | |
19 | + end | |
20 | +end | ... | ... |
plugins/custom_forms/db/migrate/20120727174506_create_custom_forms_plugin_fields.rb
0 → 100644
... | ... | @@ -0,0 +1,21 @@ |
1 | +class CreateCustomFormsPluginFields < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + create_table :custom_forms_plugin_fields do |t| | |
4 | + t.string :name | |
5 | + t.string :slug | |
6 | + t.string :type | |
7 | + t.string :default_value | |
8 | + t.string :choices | |
9 | + t.float :minimum | |
10 | + t.float :maximum | |
11 | + t.references :form | |
12 | + t.boolean :mandatory, :default => false | |
13 | + t.boolean :multiple | |
14 | + t.boolean :list | |
15 | + end | |
16 | + end | |
17 | + | |
18 | + def self.down | |
19 | + drop_table :custom_forms_plugin_fields | |
20 | + end | |
21 | +end | ... | ... |
plugins/custom_forms/db/migrate/20120727175250_create_custom_forms_plugin_answers.rb
0 → 100644
... | ... | @@ -0,0 +1,13 @@ |
1 | +class CreateCustomFormsPluginAnswers < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + create_table :custom_forms_plugin_answers do |t| | |
4 | + t.text :value | |
5 | + t.references :field | |
6 | + t.references :submission | |
7 | + end | |
8 | + end | |
9 | + | |
10 | + def self.down | |
11 | + drop_table :custom_forms_plugin_answers | |
12 | + end | |
13 | +end | ... | ... |
plugins/custom_forms/db/migrate/20120727180512_create_custom_forms_plugin_submissions.rb
0 → 100644
... | ... | @@ -0,0 +1,15 @@ |
1 | +class CreateCustomFormsPluginSubmissions < ActiveRecord::Migration | |
2 | + def self.up | |
3 | + create_table :custom_forms_plugin_submissions do |t| | |
4 | + t.string :author_name | |
5 | + t.string :author_email | |
6 | + t.references :profile | |
7 | + t.references :form | |
8 | + t.timestamps | |
9 | + end | |
10 | + end | |
11 | + | |
12 | + def self.down | |
13 | + drop_table :custom_forms_plugin_submissions | |
14 | + end | |
15 | +end | ... | ... |
... | ... | @@ -0,0 +1,21 @@ |
1 | +require 'ext/role_assignment_trigger' | |
2 | + | |
3 | +class CustomFormsPlugin < Noosfero::Plugin | |
4 | + | |
5 | + def self.plugin_name | |
6 | + "Custom Forms" | |
7 | + end | |
8 | + | |
9 | + def self.plugin_description | |
10 | + _("Enables the creation of forms.") | |
11 | + end | |
12 | + | |
13 | + def stylesheet? | |
14 | + true | |
15 | + end | |
16 | + | |
17 | + def control_panel_buttons | |
18 | + {:title => _('Manage Forms'), :icon => 'custom-forms', :url => {:controller => 'custom_forms_plugin_myprofile'}} | |
19 | + end | |
20 | + | |
21 | +end | ... | ... |
... | ... | @@ -0,0 +1,14 @@ |
1 | +class CustomFormsPlugin::Answer < Noosfero::Plugin::ActiveRecord | |
2 | + belongs_to :field, :class_name => 'CustomFormsPlugin::Field' | |
3 | + belongs_to :submission, :class_name => 'CustomFormsPlugin::Submission' | |
4 | + | |
5 | + validates_presence_of :field | |
6 | + validate :value_mandatory, :if => 'field.present?' | |
7 | + | |
8 | + def value_mandatory | |
9 | + if field.mandatory && value.blank? | |
10 | + errors.add(field.slug.to_sym, _("is mandatory.").fix_i18n) | |
11 | + end | |
12 | + end | |
13 | +end | |
14 | + | ... | ... |
... | ... | @@ -0,0 +1,16 @@ |
1 | +class CustomFormsPlugin::Field < ActiveRecord::Base | |
2 | + set_table_name :custom_forms_plugin_fields | |
3 | + | |
4 | + validates_presence_of :form, :name | |
5 | + validates_uniqueness_of :slug, :scope => :form_id | |
6 | + | |
7 | + belongs_to :form, :class_name => 'CustomFormsPlugin::Form', :dependent => :destroy | |
8 | + has_many :answers, :class_name => 'CustomFormsPlugin::Answer' | |
9 | + | |
10 | + serialize :choices, Hash | |
11 | + | |
12 | + before_validation do |field| | |
13 | + field.slug = field.name.to_slug if field.name.present? | |
14 | + end | |
15 | +end | |
16 | + | ... | ... |
... | ... | @@ -0,0 +1,70 @@ |
1 | +class CustomFormsPlugin::Form < Noosfero::Plugin::ActiveRecord | |
2 | + belongs_to :profile | |
3 | + | |
4 | + has_many :fields, :class_name => 'CustomFormsPlugin::Field' | |
5 | + has_many :submissions, :class_name => 'CustomFormsPlugin::Submission' | |
6 | + | |
7 | + serialize :access | |
8 | + | |
9 | + validates_presence_of :profile, :name | |
10 | + validates_uniqueness_of :slug, :scope => :profile_id | |
11 | + validate :access_format | |
12 | + | |
13 | + before_validation do |form| | |
14 | + form.slug = form.name.to_slug if form.name.present? | |
15 | + form.access = nil if form.access.blank? | |
16 | + end | |
17 | + | |
18 | + named_scope :from, lambda {|profile| {:conditions => {:profile_id => profile.id}}} | |
19 | + named_scope :on_memberships, {:conditions => {:on_membership => true}} | |
20 | +=begin | |
21 | + named_scope :accessible_to lambda do |profile| | |
22 | + #TODO should verify is profile is associated with the form owner | |
23 | + profile_associated = ??? | |
24 | + {:conditions => [" | |
25 | + access IS NULL OR | |
26 | + (access='logged' AND :profile_present) OR | |
27 | + (access='associated' AND :profile_associated) OR | |
28 | + :profile_id in access | |
29 | + ", {:profile_present => profile.present?, :profile_associated => ???, :profile_id => profile.id}]} | |
30 | + end | |
31 | +=end | |
32 | + | |
33 | + def expired? | |
34 | + (begining.present? && Time.now < begining) || (ending.present? && Time.now > ending) | |
35 | + end | |
36 | + | |
37 | + def accessible_to(target) | |
38 | + return true if access.nil? || target == profile | |
39 | + return false if target.nil? | |
40 | + return true if access == 'logged' | |
41 | + return true if access == 'associated' && ((profile.organization? && profile.members.include?(target)) || (profile.person? && profile.friends.include?(target))) | |
42 | + return true if access.kind_of?(Integer) && target.id == access | |
43 | + return true if access.kind_of?(Array) && access.include?(target.id) | |
44 | + end | |
45 | + | |
46 | + private | |
47 | + | |
48 | + def access_format | |
49 | + if access.present? | |
50 | + if access.kind_of?(String) | |
51 | + if access != 'logged' && access != 'associated' | |
52 | + errors.add(:access, _('Invalid string format of access.')) | |
53 | + end | |
54 | + elsif access.kind_of?(Integer) | |
55 | + if !Profile.exists?(access) | |
56 | + errors.add(:access, _('There is no profile with the provided id.')) | |
57 | + end | |
58 | + elsif access.kind_of?(Array) | |
59 | + access.each do |value| | |
60 | + if !value.kind_of?(Integer) || !Profile.exists?(value) | |
61 | + errors.add(:access, _('There is no profile with the provided id.')) | |
62 | + break | |
63 | + end | |
64 | + end | |
65 | + else | |
66 | + errors.add(:access, _('Invalid type format of access.')) | |
67 | + end | |
68 | + end | |
69 | + end | |
70 | +end | ... | ... |
... | ... | @@ -0,0 +1,119 @@ |
1 | +module CustomFormsPlugin::Helper | |
2 | + def access_text(form) | |
3 | + return _('Public') if form.access.nil? | |
4 | + return _('Logged users') if form.access == 'logged' | |
5 | + if form.access == 'associated' | |
6 | + return _('Members') if form.profile.organization? | |
7 | + return _('Friends') if form.profile.person? | |
8 | + end | |
9 | + return _('Custom') | |
10 | + end | |
11 | + | |
12 | + def period_range(form) | |
13 | + if form.begining.blank? && form.ending.blank? | |
14 | + _('Always') | |
15 | + elsif form.begining.present? && form.ending.blank? | |
16 | + ('From %s') % time_format(form.begining) | |
17 | + elsif form.begining.blank? && form.ending.present? | |
18 | + _('Until %s') % time_format(form.ending) | |
19 | + elsif form.begining.present? && form.ending.present? | |
20 | + _('From %s until %s') % [time_format(form.begining), time_format(form.ending)] | |
21 | + end | |
22 | + end | |
23 | + | |
24 | + def time_format(time) | |
25 | + minutes = (time.min == 0) ? '' : ':%M' | |
26 | + hour = (time.hour == 0 && minutes.blank?) ? '' : ' %H' | |
27 | + h = hour.blank? ? '' : 'h' | |
28 | + time.strftime("%Y-%m-%d#{hour+minutes+h}") | |
29 | + end | |
30 | + | |
31 | + # TODO add the custom option that should offer the user the hability to | |
32 | + # choose the profiles one by one, using something like tokeninput | |
33 | + def access_options(profile) | |
34 | + associated = profile.organization? ? _('Members') : _('Friends') | |
35 | + [ | |
36 | + [_('Public'), nil ], | |
37 | + [_('Logged users'), 'logged' ], | |
38 | + [ associated, 'associated'], | |
39 | + ] | |
40 | + end | |
41 | + | |
42 | + def type_options | |
43 | + [ | |
44 | + [_('Text'), 'text_field' ], | |
45 | + [_('Select'), 'select_field'] | |
46 | + ] | |
47 | + end | |
48 | + | |
49 | + def type_to_label(type) | |
50 | + map = { | |
51 | + 'text_field' => _('Text'), | |
52 | + 'select_field' => _('Select') | |
53 | + } | |
54 | + map[type_for_options(type)] | |
55 | + end | |
56 | + | |
57 | + def type_for_options(type) | |
58 | + type.to_s.split(':').last.underscore | |
59 | + end | |
60 | + | |
61 | + def display_custom_field(field, submission, form) | |
62 | + answer = submission.answers.select{|answer| answer.field == field}.first | |
63 | + field_tag = send("display_#{type_for_options(field.class)}",field, answer, form) | |
64 | + if field.mandatory? && !radio_button?(field) && !check_box?(field) && submission.id.nil? | |
65 | + required(labelled_form_field(field.name, field_tag)) | |
66 | + else | |
67 | + labelled_form_field(field.name, field_tag) | |
68 | + end | |
69 | + end | |
70 | + | |
71 | + def display_text_field(field, answer, form) | |
72 | + value = answer.present? ? answer.value : field.default_value | |
73 | + text_field(form, field.name.to_slug, :value => value, :disabled => answer.present?) | |
74 | + end | |
75 | + | |
76 | + def display_select_field(field, answer, form) | |
77 | + if field.list && field.multiple | |
78 | + selected = answer.present? ? answer.value.split(',') : [] | |
79 | + select_tag "#{form}[#{field.name.to_slug}]", options_for_select(field.choices.to_a, selected), :multiple => true, :size => field.choices.size, :disabled => answer.present? | |
80 | + elsif !field.list && field.multiple | |
81 | + field.choices.map do |name, value| | |
82 | + default = answer.present? ? answer.value.split(',').include?(value) : false | |
83 | + labelled_check_box name, "#{form}[#{field.name.to_slug}][#{value}]", '1', default, :disabled => answer.present? | |
84 | + end.join("\n") | |
85 | + elsif field.list && !field.multiple | |
86 | + selected = answer.present? ? answer.value.split(',') : [] | |
87 | + select_tag "#{form}[#{field.name.to_slug}]", options_for_select([['','']] + field.choices.to_a, selected), :disabled => answer.present? | |
88 | + elsif !field.list && !field.multiple | |
89 | + field.choices.map do |name, value| | |
90 | + default = answer.present? ? answer.value == value : true | |
91 | + labelled_radio_button name, "#{form}[#{field.name.to_slug}]", value, default, :disabled => answer.present? | |
92 | + end.join("\n") | |
93 | + end | |
94 | + end | |
95 | + | |
96 | + def radio_button?(field) | |
97 | + type_for_options(field.class) == 'select_field' && !field.list && !field.multiple | |
98 | + end | |
99 | + | |
100 | + def check_box?(field) | |
101 | + type_for_options(field.class) == 'select_field' && !field.list && field.multiple | |
102 | + end | |
103 | + | |
104 | + def build_answers(submission, form) | |
105 | + answers = [] | |
106 | + submission.each do |slug, value| | |
107 | + field = form.fields.select {|field| field.slug==slug}.first | |
108 | + if value.kind_of?(String) | |
109 | + final_value = value | |
110 | + elsif value.kind_of?(Array) | |
111 | + final_value = value.join(',') | |
112 | + elsif value.kind_of?(Hash) | |
113 | + final_value = value.map {|option, present| present == '1' ? option : nil}.compact.join(',') | |
114 | + end | |
115 | + answers << CustomFormsPlugin::Answer.new(:field => field, :value => final_value) | |
116 | + end | |
117 | + answers | |
118 | + end | |
119 | +end | ... | ... |
plugins/custom_forms/lib/custom_forms_plugin/membership_survey.rb
0 → 100644
... | ... | @@ -0,0 +1,47 @@ |
1 | +class CustomFormsPlugin::MembershipSurvey < Task | |
2 | + | |
3 | + settings_items :form_id, :submission | |
4 | + validates_presence_of :form_id | |
5 | + | |
6 | + include CustomFormsPlugin::Helper | |
7 | + | |
8 | + def perform | |
9 | + form = CustomFormsPlugin::Form.find(form_id) | |
10 | + answers = build_answers(submission, form) | |
11 | + s = CustomFormsPlugin::Submission.create!(:form => form, :profile => target) | |
12 | + answers.map {|answer| answer.submission = s; answer.save!} | |
13 | + end | |
14 | + | |
15 | + def title | |
16 | + _("Membership survey") | |
17 | + end | |
18 | + | |
19 | + def subject | |
20 | + nil | |
21 | + end | |
22 | + | |
23 | + def linked_subject | |
24 | + nil | |
25 | + end | |
26 | + | |
27 | + def information | |
28 | + {:message => _('%{requestor} wants you to fill in some information.')} | |
29 | + end | |
30 | + | |
31 | + def accept_details | |
32 | + true | |
33 | + end | |
34 | + | |
35 | + def icon | |
36 | + {:type => :profile_image, :profile => requestor, :url => requestor.url} | |
37 | + end | |
38 | + | |
39 | + def target_notification_message | |
40 | + _('After joining %{requestor}, the administrators of this organization | |
41 | + wants you to fill in some further information.') % {:requestor => requestor.name} | |
42 | + end | |
43 | + | |
44 | + def target_notification_description | |
45 | + _('%{requestor} wants to fill in some further information.') % {:requestor => requestor.name} | |
46 | + end | |
47 | +end | ... | ... |
plugins/custom_forms/lib/custom_forms_plugin/select_field.rb
0 → 100644
plugins/custom_forms/lib/custom_forms_plugin/submission.rb
0 → 100644
... | ... | @@ -0,0 +1,12 @@ |
1 | +class CustomFormsPlugin::Submission < Noosfero::Plugin::ActiveRecord | |
2 | + belongs_to :form, :class_name => 'CustomFormsPlugin::Form' | |
3 | + belongs_to :profile | |
4 | + | |
5 | + has_many :answers, :class_name => 'CustomFormsPlugin::Answer' | |
6 | + | |
7 | + validates_presence_of :form | |
8 | + validates_presence_of :author_name, :author_email, :if => lambda {|submission| submission.profile.nil?} | |
9 | + validates_uniqueness_of :author_email, :scope => :form_id, :allow_nil => true | |
10 | + validates_format_of :author_email, :with => Noosfero::Constants::EMAIL_FORMAT, :if => (lambda {|submission| !submission.author_email.blank?}) | |
11 | +end | |
12 | + | ... | ... |
plugins/custom_forms/lib/custom_forms_plugin/text_field.rb
0 → 100644
... | ... | @@ -0,0 +1,30 @@ |
1 | +module RoleAssignmentTrigger | |
2 | + def self.included(base) | |
3 | + base.class_eval do | |
4 | + before_create do |ra| | |
5 | + profile = ra.resource | |
6 | + person = ra.accessor | |
7 | + ok = !profile.nil? && !person.nil? && profile.environment.present? | |
8 | + if ok && profile.environment.plugin_enabled?(CustomFormsPlugin) && !person.is_member_of?(profile) | |
9 | + CustomFormsPlugin::Form.from(profile).on_memberships.each do |form| | |
10 | + CustomFormsPlugin::MembershipSurvey.create!(:requestor => profile, :target => person, :form_id => form.id) | |
11 | + end | |
12 | + end | |
13 | + end | |
14 | + | |
15 | + after_destroy do |ra| | |
16 | + profile = ra.resource | |
17 | + person = ra.accessor | |
18 | + ok = !profile.nil? && !person.nil? && profile.environment.present? | |
19 | + if ok && profile.environment.plugin_enabled?(CustomFormsPlugin) && !person.is_member_of?(profile) | |
20 | + CustomFormsPlugin::Form.from(profile).on_memberships.each do |form| | |
21 | + task = person.tasks.pending.select {|task| task.kind_of?(CustomFormsPlugin::MembershipSurvey) && task.form_id == form.id}.first | |
22 | + task.cancel if task | |
23 | + end | |
24 | + end | |
25 | + end | |
26 | + end | |
27 | + end | |
28 | +end | |
29 | + | |
30 | +RoleAssignment.send :include, RoleAssignmentTrigger | ... | ... |
... | ... | @@ -0,0 +1,141 @@ |
1 | +jQuery('.icon-edit').live('click', function() { | |
2 | + elem = this; | |
3 | + jQuery.fn.colorbox({ | |
4 | + overlayClose: false, | |
5 | + escKey: false, | |
6 | + inline: true, | |
7 | + href: function(){ | |
8 | + id = jQuery(elem).attr('field_id'); | |
9 | + type = jQuery('#fields_'+id+'_type').val().split('_')[0]; | |
10 | + selector = '#edit-'+type+'-'+id | |
11 | + jQuery(selector).show(); | |
12 | + return selector | |
13 | + } | |
14 | + }); | |
15 | + return false; | |
16 | +}); | |
17 | + | |
18 | +jQuery('.remove-field').live('click', function(){ | |
19 | + id = jQuery(this).attr('field_id'); | |
20 | + jQuery('#field-'+id).slideDown(function(){ | |
21 | + jQuery('#field-'+id).remove(); | |
22 | + }); | |
23 | + return false | |
24 | +}); | |
25 | + | |
26 | +jQuery('.remove-option').live('click', function(){ | |
27 | + field_id = jQuery(this).attr('field_id'); | |
28 | + option_id = jQuery(this).attr('option_id'); | |
29 | + selector = '#field-'+field_id+'-option-'+option_id | |
30 | + jQuery(selector).slideDown(function(){ | |
31 | + jQuery(selector).remove(); | |
32 | + jQuery.colorbox.resize(); | |
33 | + }); | |
34 | + return false | |
35 | +}); | |
36 | + | |
37 | +function updateEditText(id){ | |
38 | + new_id = id+1 | |
39 | + jQuery('#edit-text-'+id).attr('id', 'edit-text-'+new_id); | |
40 | + input = jQuery('#edit-text-'+new_id+' input'); | |
41 | + jQuery('#edit-text-'+new_id+' .colorbox-ok-button').attr('div_id', 'edit-text-'+new_id); | |
42 | + input.attr('id', input.attr('id').replace(id,new_id)); | |
43 | + input.attr('name', input.attr('name').replace(id,new_id)); | |
44 | + label = jQuery('#edit-text-'+new_id+' label'); | |
45 | + label.attr('for', label.attr('for').replace(id,new_id)); | |
46 | +} | |
47 | + | |
48 | +function updateEditSelect(id){ | |
49 | + new_id = id+1 | |
50 | + jQuery('#edit-select-'+id).attr('id', 'edit-select-'+new_id); | |
51 | + jQuery('#edit-select-'+new_id+' .colorbox-ok-button').attr('div_id', 'edit-select-'+new_id); | |
52 | + jQuery('tr[id^=field-'+id+'-option').each(function(id, element){ | |
53 | + jQuery(element).attr('id', jQuery(element).attr('id').replace('field-'+id,'field-'+new_id)); | |
54 | + }); | |
55 | + jQuery('#edit-select-'+new_id+' label').each(function(index, element){ | |
56 | + label = jQuery(element); | |
57 | + label.attr('for', label.attr('for').replace(id,new_id)); | |
58 | + }); | |
59 | + jQuery('#edit-select-'+new_id+' input').each(function(index, element){ | |
60 | + input = jQuery(element); | |
61 | + input.attr('id', input.attr('id').replace(id,new_id)); | |
62 | + input.attr('name', input.attr('name').replace(id,new_id)); | |
63 | + }); | |
64 | + jQuery('#edit-select-'+new_id+' .remove-option').each(function(index, element){ | |
65 | + jQuery(element).attr('field_id',new_id); | |
66 | + }); | |
67 | + jQuery('#edit-select-'+new_id+' .new-option').attr('field_id',new_id); | |
68 | + jQuery('#edit-select-'+new_id+' #empty-option-'+id).attr('id','empty-option-'+new_id); | |
69 | +} | |
70 | + | |
71 | +function updateEmptyField(id){ | |
72 | + id = parseInt(id); | |
73 | + empty_field = jQuery('#empty-field'); | |
74 | + empty_field.attr('last_id', (id + 1).toString()); | |
75 | + jQuery('#empty-field input').each(function(index, element){ | |
76 | + new_id = jQuery(element).attr('id').replace(id,id+1); | |
77 | + jQuery(element).attr('id', new_id); | |
78 | + new_name = jQuery(element).attr('name').replace(id,id+1); | |
79 | + jQuery(element).attr('name', new_name); | |
80 | + }); | |
81 | + jQuery('#empty-field select').each(function(index, element){ | |
82 | + new_id = jQuery(element).attr('id').replace(id,id+1); | |
83 | + jQuery(element).attr('id', new_id); | |
84 | + new_name = jQuery(element).attr('name').replace(id,id+1); | |
85 | + jQuery(element).attr('name', new_name); | |
86 | + }); | |
87 | + jQuery('#empty-field a').each(function(index, element){ | |
88 | + jQuery(element).attr('field_id', id+1); | |
89 | + }); | |
90 | + updateEditText(id); | |
91 | + updateEditSelect(id); | |
92 | +} | |
93 | + | |
94 | +function updateEmptyOption(field_id, option_id){ | |
95 | + field_id = parseInt(field_id); | |
96 | + option_id = parseInt(option_id); | |
97 | + new_option_id = option_id+1; | |
98 | + empty_option = jQuery('#empty-option-'+field_id); | |
99 | + empty_option.attr('option_id',new_option_id); | |
100 | + jQuery('#empty-option-'+field_id+' .remove-option').attr('option_id', new_option_id); | |
101 | + | |
102 | + name_id = ' #fields_'+field_id+'_choices_'+option_id+'_name'; | |
103 | + jQuery('#empty-option-'+field_id+name_id).attr('name', 'fields['+field_id+'][choices]['+new_option_id+'][name]'); | |
104 | + jQuery('#empty-option-'+field_id+name_id).attr('id', 'fields_'+field_id+'_choices_'+new_option_id+'_name'); | |
105 | + | |
106 | + value_id = ' #fields_'+field_id+'_choices_'+option_id+'_value'; | |
107 | + jQuery('#empty-option-'+field_id+value_id).attr('name', 'fields['+field_id+'][choices]['+new_option_id+'][value]'); | |
108 | + jQuery('#empty-option-'+field_id+value_id).attr('id', 'fields_'+field_id+'_choices_'+new_option_id+'_value'); | |
109 | +} | |
110 | + | |
111 | +jQuery('#new-field').live('click', function(){ | |
112 | + empty_field = jQuery('#empty-field'); | |
113 | + id = empty_field.attr('last_id'); | |
114 | + edit_text = jQuery('#edit-text-'+id); | |
115 | + edit_select = jQuery('#edit-select-'+id); | |
116 | + new_field = empty_field.clone(); | |
117 | + new_field.attr('id','field-'+id); | |
118 | + new_field.insertBefore(empty_field).slideDown(); | |
119 | + edit_text.clone().insertAfter(edit_text); | |
120 | + edit_select.clone().insertAfter(edit_select); | |
121 | + updateEmptyField(id); | |
122 | + return false | |
123 | +}); | |
124 | + | |
125 | +jQuery('.new-option').live('click', function(){ | |
126 | + field_id = jQuery(this).attr('field_id'); | |
127 | + empty_option = jQuery('#empty-option-'+field_id); | |
128 | + option_id = empty_option.attr('option_id'); | |
129 | + new_option = empty_option.clone(); | |
130 | + new_option.attr('id','field-'+field_id+'-option-'+option_id); | |
131 | + new_option.insertBefore(empty_option).slideDown(); | |
132 | + jQuery.colorbox.resize(); | |
133 | + updateEmptyOption(field_id, option_id); | |
134 | + return false | |
135 | +}); | |
136 | + | |
137 | +jQuery('.colorbox-ok-button').live('click', function(){ | |
138 | + jQuery('#'+jQuery(this).attr('div_id')).hide(); | |
139 | + jQuery.colorbox.close(); | |
140 | + return false | |
141 | +}); | ... | ... |
4.05 KB
... | ... | @@ -0,0 +1,30 @@ |
1 | +.controller-profile_editor a.control-panel-custom-forms, | |
2 | +.controller-profile_editor .msie6 a.control-panel-custom-forms { | |
3 | + background-image: url(/plugins/custom_forms/icons/custom-forms.png) | |
4 | +} | |
5 | + | |
6 | +.action-table { | |
7 | + width: 100%; | |
8 | + overflow: hidden; | |
9 | +} | |
10 | + | |
11 | +.action-table th, | |
12 | +.action-table td{ | |
13 | + text-align: center; | |
14 | +} | |
15 | + | |
16 | +.action-table td{ | |
17 | + cursor: move; | |
18 | +} | |
19 | + | |
20 | +.action-table .actions{ | |
21 | + white-space: nowrap; | |
22 | +} | |
23 | + | |
24 | +.action-table .new-item{ | |
25 | + background-color: #EEE; | |
26 | +} | |
27 | + | |
28 | +.edit-information { | |
29 | + display: none; | |
30 | +} | ... | ... |
plugins/custom_forms/test/functional/custom_forms_plugin_myprofile_controller_test.rb
0 → 100644
... | ... | @@ -0,0 +1,114 @@ |
1 | +require File.dirname(__FILE__) + '/../../../../test/test_helper' | |
2 | +require File.dirname(__FILE__) + '/../../controllers/custom_forms_plugin_myprofile_controller' | |
3 | + | |
4 | +# Re-raise errors caught by the controller. | |
5 | +class CustomFormsPluginMyprofileController; def rescue_action(e) raise e end; end | |
6 | + | |
7 | +class CustomFormsPluginMyprofileControllerTest < ActionController::TestCase | |
8 | + def setup | |
9 | + @controller = CustomFormsPluginMyprofileController.new | |
10 | + @request = ActionController::TestRequest.new | |
11 | + @response = ActionController::TestResponse.new | |
12 | + @profile = create_user('profile').person | |
13 | + login_as(@profile.identifier) | |
14 | + environment = Environment.default | |
15 | + environment.enable_plugin(CustomFormsPlugin) | |
16 | + end | |
17 | + | |
18 | + attr_reader :profile | |
19 | + | |
20 | + should 'list forms associated with profile' do | |
21 | + another_profile = fast_create(Profile) | |
22 | + f1 = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software') | |
23 | + f2 = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Open Source') | |
24 | + f3 = CustomFormsPlugin::Form.create!(:profile => another_profile, :name => 'Open Source') | |
25 | + | |
26 | + get :index, :profile => profile.identifier | |
27 | + | |
28 | + assert_includes assigns(:forms), f1 | |
29 | + assert_includes assigns(:forms), f2 | |
30 | + assert_not_includes assigns(:forms), f3 | |
31 | + end | |
32 | + | |
33 | + should 'destroy form' do | |
34 | + form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software') | |
35 | + assert CustomFormsPlugin::Form.exists?(form.id) | |
36 | + post :remove, :profile => profile.identifier, :id => form.id | |
37 | + assert !CustomFormsPlugin::Form.exists?(form.id) | |
38 | + end | |
39 | + | |
40 | + should 'create a form' do | |
41 | + format = '%Y-%m-%d %H:%M' | |
42 | + begining = Time.now.strftime(format) | |
43 | + ending = (Time.now + 1.day).strftime('%Y-%m-%d %H:%M') | |
44 | + assert_difference CustomFormsPlugin::Form, :count, 1 do | |
45 | + post :create, :profile => profile.identifier, | |
46 | + :form => { | |
47 | + :name => 'My Form', | |
48 | + :access => 'logged', | |
49 | + :begining => begining, | |
50 | + :ending => ending, | |
51 | + :description => 'Cool form'}, | |
52 | + :fields => { | |
53 | + 1 => { | |
54 | + :name => 'Name', | |
55 | + :default_value => 'Jack', | |
56 | + :type => 'text_field' | |
57 | + }, | |
58 | + 2 => { | |
59 | + :name => 'Color', | |
60 | + :list => '1', | |
61 | + :type => 'select_field', | |
62 | + :choices => { | |
63 | + 1 => {:name => 'Red', :value => 'red'}, | |
64 | + 2 => {:name => 'Blue', :value => 'blue'}, | |
65 | + 3 => {:name => 'Black', :value => 'black'} | |
66 | + } | |
67 | + } | |
68 | + } | |
69 | + end | |
70 | + | |
71 | + form = CustomFormsPlugin::Form.find_by_name('My Form') | |
72 | + assert_equal 'logged', form.access | |
73 | + assert_equal begining, form.begining.strftime(format) | |
74 | + assert_equal ending, form.ending.strftime(format) | |
75 | + assert_equal 'Cool form', form.description | |
76 | + assert_equal 2, form.fields.count | |
77 | + | |
78 | + f1 = form.fields.first | |
79 | + f2 = form.fields.last | |
80 | + | |
81 | + assert_equal 'Name', f1.name | |
82 | + assert_equal 'Jack', f1.default_value | |
83 | + assert f1.kind_of?(CustomFormsPlugin::TextField) | |
84 | + | |
85 | + assert_equal 'Color', f2.name | |
86 | + assert_equal 'red', f2.choices['Red'] | |
87 | + assert_equal 'blue', f2.choices['Blue'] | |
88 | + assert_equal 'black', f2.choices['Black'] | |
89 | + assert f2.list | |
90 | + assert f2.kind_of?(CustomFormsPlugin::SelectField) | |
91 | + end | |
92 | + | |
93 | + should 'edit a form' do | |
94 | + form = CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software') | |
95 | + field = CustomFormsPlugin::TextField.create!(:form => form, :name => 'License') | |
96 | + format = '%Y-%m-%d %H:%M' | |
97 | + begining = Time.now.strftime(format) | |
98 | + ending = (Time.now + 1.day).strftime('%Y-%m-%d %H:%M') | |
99 | + | |
100 | + post :edit, :profile => profile.identifier, :id => form.id, | |
101 | + :form => {:name => 'My Form', :access => 'logged', :begining => begining, :ending => ending, :description => 'Cool form'}, | |
102 | + :fields => {1 => {:real_id => field.id.to_s, :name => 'Source'}} | |
103 | + | |
104 | + form.reload | |
105 | + field.reload | |
106 | + | |
107 | + assert_equal 'logged', form.access | |
108 | + assert_equal begining, form.begining.strftime(format) | |
109 | + assert_equal ending, form.ending.strftime(format) | |
110 | + assert_equal 'Cool form', form.description | |
111 | + assert_equal 'Source', field.name | |
112 | + end | |
113 | +end | |
114 | + | ... | ... |
plugins/custom_forms/test/unit/custom_forms_plugin/answer_test.rb
0 → 100644
... | ... | @@ -0,0 +1,38 @@ |
1 | +require File.dirname(__FILE__) + '/../../../../../test/test_helper' | |
2 | + | |
3 | +class CustomFormsPlugin::AnswerTest < ActiveSupport::TestCase | |
4 | + should 'validates presence of field' do | |
5 | + answer = CustomFormsPlugin::Answer.new | |
6 | + answer.valid? | |
7 | + assert answer.errors.invalid?(:field) | |
8 | + | |
9 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
10 | + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form) | |
11 | + answer.field = field | |
12 | + answer.valid? | |
13 | + assert !answer.errors.invalid?(:field) | |
14 | + end | |
15 | + | |
16 | + should 'belong to a submission' do | |
17 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
18 | + submission = CustomFormsPlugin::Submission.create!(:form => form, :profile => fast_create(Profile)) | |
19 | + answer = CustomFormsPlugin::Answer.new | |
20 | + answer.submission = submission | |
21 | + | |
22 | + assert_equal submission, answer.submission | |
23 | + end | |
24 | + | |
25 | + should 'require presence of value if field is mandatory' do | |
26 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
27 | + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form, :mandatory => true) | |
28 | + answer = CustomFormsPlugin::Answer.new(:field => field) | |
29 | + answer.valid? | |
30 | + assert answer.errors.invalid?(field.slug.to_sym) | |
31 | + | |
32 | + answer.value = "GPL" | |
33 | + answer.valid? | |
34 | + assert !answer.errors.invalid?(field.slug.to_sym) | |
35 | + end | |
36 | + | |
37 | +end | |
38 | + | ... | ... |
plugins/custom_forms/test/unit/custom_forms_plugin/field_test.rb
0 → 100644
... | ... | @@ -0,0 +1,64 @@ |
1 | +require File.dirname(__FILE__) + '/../../../../../test/test_helper' | |
2 | + | |
3 | +class CustomFormsPlugin::FieldTest < ActiveSupport::TestCase | |
4 | + should 'validate presence of form' do | |
5 | + field = CustomFormsPlugin::Field.new | |
6 | + field.valid? | |
7 | + assert field.errors.invalid?(:form) | |
8 | + assert field.errors.invalid?(:name) | |
9 | + | |
10 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
11 | + field.form = form | |
12 | + field.name = 'License' | |
13 | + field.valid? | |
14 | + assert !field.errors.invalid?(:form) | |
15 | + assert !field.errors.invalid?(:name) | |
16 | + end | |
17 | + | |
18 | + should 'set slug before validation based on name' do | |
19 | + field = CustomFormsPlugin::Field.new(:name => 'Name') | |
20 | + field.valid? | |
21 | + assert_equal field.name.to_slug, field.slug | |
22 | + end | |
23 | + | |
24 | + should 'validate uniqueness of slug scoped on the form' do | |
25 | + form1 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
26 | + form2 = CustomFormsPlugin::Form.create!(:name => 'Open Source', :profile => fast_create(Profile)) | |
27 | + f1 = CustomFormsPlugin::Field.create!(:name => 'License', :form => form1) | |
28 | + f2 = CustomFormsPlugin::Field.new(:name => 'License', :form => form1) | |
29 | + f3 = CustomFormsPlugin::Field.new(:name => 'License', :form => form2) | |
30 | + | |
31 | + f2.valid? | |
32 | + f3.valid? | |
33 | + | |
34 | + assert f2.errors.invalid?(:slug) | |
35 | + assert !f3.errors.invalid?(:slug) | |
36 | + end | |
37 | + | |
38 | + should 'set mandatory field as false by default' do | |
39 | + field = CustomFormsPlugin::Field.new | |
40 | + assert !field.mandatory | |
41 | + end | |
42 | + | |
43 | + should 'have answers' do | |
44 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
45 | + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form) | |
46 | + a1 = CustomFormsPlugin::Answer.create!(:field => field) | |
47 | + a2 = CustomFormsPlugin::Answer.create!(:field => field) | |
48 | + | |
49 | + assert_includes field.answers, a1 | |
50 | + assert_includes field.answers, a2 | |
51 | + end | |
52 | + | |
53 | + should 'serialize choices into a hash' do | |
54 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
55 | + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form) | |
56 | + field.choices = {'First' => 1, 'Second' => 2, 'Third' => 3} | |
57 | + field.save! | |
58 | + | |
59 | + assert_equal 1, field.choices['First'] | |
60 | + assert_equal 2, field.choices['Second'] | |
61 | + assert_equal 3, field.choices['Third'] | |
62 | + end | |
63 | +end | |
64 | + | ... | ... |
plugins/custom_forms/test/unit/custom_forms_plugin/form_test.rb
0 → 100644
... | ... | @@ -0,0 +1,172 @@ |
1 | +require File.dirname(__FILE__) + '/../../../../../test/test_helper' | |
2 | + | |
3 | +class CustomFormsPlugin::FormTest < ActiveSupport::TestCase | |
4 | + should 'validates presence of a profile and a name' do | |
5 | + form = CustomFormsPlugin::Form.new | |
6 | + form.valid? | |
7 | + assert form.errors.invalid?(:profile) | |
8 | + assert form.errors.invalid?(:name) | |
9 | + | |
10 | + form.profile = fast_create(Profile) | |
11 | + form.name = 'Free Software' | |
12 | + form.valid? | |
13 | + assert !form.errors.invalid?(:profile) | |
14 | + assert !form.errors.invalid?(:name) | |
15 | + end | |
16 | + | |
17 | + should 'have many fields including fields subclasses' do | |
18 | + form = CustomFormsPlugin::Form.create!(:profile => fast_create(Profile), :name => 'Free Software') | |
19 | + f1 = CustomFormsPlugin::Field.create!(:form => form, :name => 'License') | |
20 | + f2 = CustomFormsPlugin::Field.create!(:form => form, :name => 'Code') | |
21 | + f3 = CustomFormsPlugin::TextField.create!(:form => form, :name => 'Developer') | |
22 | + | |
23 | + assert_includes form.fields, f1 | |
24 | + assert_includes form.fields, f2 | |
25 | + assert_includes form.fields, f3 | |
26 | + end | |
27 | + | |
28 | + should 'have many submissions' do | |
29 | + form = CustomFormsPlugin::Form.create!(:profile => fast_create(Profile), :name => 'Free Software') | |
30 | + s1 = CustomFormsPlugin::Submission.create!(:form => form, :profile => fast_create(Profile)) | |
31 | + s2 = CustomFormsPlugin::Submission.create!(:form => form, :profile => fast_create(Profile)) | |
32 | + | |
33 | + assert_includes form.submissions, s1 | |
34 | + assert_includes form.submissions, s2 | |
35 | + end | |
36 | + | |
37 | + should 'set slug before validation based on name' do | |
38 | + form = CustomFormsPlugin::Form.new(:name => 'Name') | |
39 | + form.valid? | |
40 | + assert_equal form.name.to_slug, form.slug | |
41 | + end | |
42 | + | |
43 | + should 'validates uniqueness of slug scoped on profile' do | |
44 | + profile = fast_create(Profile) | |
45 | + another_profile = fast_create(Profile) | |
46 | + CustomFormsPlugin::Form.create!(:profile => profile, :name => 'Free Software') | |
47 | + form = CustomFormsPlugin::Form.new(:profile => profile, :name => 'Free Software') | |
48 | + form.valid? | |
49 | + assert form.errors.invalid?(:slug) | |
50 | + | |
51 | + form.profile = another_profile | |
52 | + form.valid? | |
53 | + assert !form.errors.invalid?(:slug) | |
54 | + end | |
55 | + | |
56 | + should 'define form expiration' do | |
57 | + form = CustomFormsPlugin::Form.new | |
58 | + assert !form.expired? | |
59 | + | |
60 | + form.begining = Time.now + 1.day | |
61 | + assert form.expired? | |
62 | + | |
63 | + form.begining = Time.now - 1.day | |
64 | + assert !form.expired? | |
65 | + | |
66 | + form.begining = nil | |
67 | + form.ending = Time.now + 1.day | |
68 | + assert !form.expired? | |
69 | + | |
70 | + form.ending = Time.now - 1.day | |
71 | + assert form.expired? | |
72 | + | |
73 | + form.begining = Time.now - 1.day | |
74 | + form.ending = Time.now + 1.day | |
75 | + assert !form.expired? | |
76 | + end | |
77 | + | |
78 | + should 'validates format of access' do | |
79 | + form = CustomFormsPlugin::Form.new | |
80 | + form.valid? | |
81 | + assert !form.errors.invalid?(:access) | |
82 | + | |
83 | + form.access = 'bli' | |
84 | + form.valid? | |
85 | + assert form.errors.invalid?(:access) | |
86 | + | |
87 | + form.access = 'logged' | |
88 | + form.valid? | |
89 | + assert !form.errors.invalid?(:access) | |
90 | + | |
91 | + form.access = 'associated' | |
92 | + form.valid? | |
93 | + assert !form.errors.invalid?(:access) | |
94 | + | |
95 | + form.access = {:bli => 1} | |
96 | + form.valid? | |
97 | + assert form.errors.invalid?(:access) | |
98 | + | |
99 | + form.access = 999 | |
100 | + form.valid? | |
101 | + assert form.errors.invalid?(:access) | |
102 | + | |
103 | + p1 = fast_create(Profile) | |
104 | + form.access = p1.id | |
105 | + form.valid? | |
106 | + assert !form.errors.invalid?(:access) | |
107 | + | |
108 | + p2 = fast_create(Profile) | |
109 | + p3 = fast_create(Profile) | |
110 | + form.access = [p1,p2,p3].map(&:id) | |
111 | + form.valid? | |
112 | + assert !form.errors.invalid?(:access) | |
113 | + end | |
114 | + | |
115 | + should 'defines who is able to access the form' do | |
116 | + owner = fast_create(Community) | |
117 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => owner) | |
118 | + assert form.accessible_to(nil) | |
119 | + | |
120 | + form.access = 'logged' | |
121 | + assert !form.accessible_to(nil) | |
122 | + person = fast_create(Person) | |
123 | + assert form.accessible_to(person) | |
124 | + | |
125 | + form.access = 'associated' | |
126 | + assert !form.accessible_to(person) | |
127 | + owner.add_member(person) | |
128 | + assert form.accessible_to(person) | |
129 | + | |
130 | + p1 = fast_create(Profile) | |
131 | + form.access = p1.id | |
132 | + assert !form.accessible_to(person) | |
133 | + assert form.accessible_to(p1) | |
134 | + | |
135 | + p2 = fast_create(Profile) | |
136 | + form.access = [person.id, p1.id] | |
137 | + assert form.accessible_to(person) | |
138 | + assert form.accessible_to(p1) | |
139 | + assert !form.accessible_to(p2) | |
140 | + form.access << p2.id | |
141 | + assert form.accessible_to(p2) | |
142 | + | |
143 | + assert form.accessible_to(owner) | |
144 | + end | |
145 | + | |
146 | + should 'have a named_scope that retrieve forms from a profile' do | |
147 | + profile = fast_create(Profile) | |
148 | + another_profile = fast_create(Profile) | |
149 | + f1 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => profile) | |
150 | + f2 = CustomFormsPlugin::Form.create!(:name => 'Open Source', :profile => profile) | |
151 | + f3 = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => another_profile) | |
152 | + scope = CustomFormsPlugin::Form.from(profile) | |
153 | + | |
154 | + assert_equal ActiveRecord::NamedScope::Scope, scope.class | |
155 | + assert_includes scope, f1 | |
156 | + assert_includes scope, f2 | |
157 | + assert_not_includes scope, f3 | |
158 | + end | |
159 | + | |
160 | + should 'have a named_scope that retrieves all forms that are triggered on membership' do | |
161 | + profile = fast_create(Profile) | |
162 | + f1 = CustomFormsPlugin::Form.create!(:name => 'On membership 1', :profile => profile, :on_membership => true) | |
163 | + f2 = CustomFormsPlugin::Form.create!(:name => 'On membership 2', :profile => profile, :on_membership => true) | |
164 | + f3 = CustomFormsPlugin::Form.create!(:name => 'Not on memberhsip', :profile => profile, :on_membership => false) | |
165 | + scope = CustomFormsPlugin::Form.from(profile).on_memberships | |
166 | + | |
167 | + assert_equal ActiveRecord::NamedScope::Scope, scope.class | |
168 | + assert_includes scope, f1 | |
169 | + assert_includes scope, f2 | |
170 | + assert_not_includes scope, f3 | |
171 | + end | |
172 | +end | ... | ... |
plugins/custom_forms/test/unit/custom_forms_plugin/membership_survey_test.rb
0 → 100644
... | ... | @@ -0,0 +1,31 @@ |
1 | +require File.dirname(__FILE__) + '/../../../../../test/test_helper' | |
2 | + | |
3 | +class CustomFormsPlugin::MembershipSurveyTest < ActiveSupport::TestCase | |
4 | + should 'validates presence of form_id' do | |
5 | + task = CustomFormsPlugin::MembershipSurvey.new | |
6 | + task.valid? | |
7 | + assert task.errors.invalid?(:form_id) | |
8 | + | |
9 | + task.form_id = 1 | |
10 | + task.valid? | |
11 | + assert !task.errors.invalid?(:form_id) | |
12 | + end | |
13 | + | |
14 | + should 'create submission with answers on perform' do | |
15 | + profile = fast_create(Profile) | |
16 | + person = fast_create(Person) | |
17 | + form = CustomFormsPlugin::Form.create!(:name => 'Simple Form', :profile => profile) | |
18 | + field = CustomFormsPlugin::Field.create!(:name => 'Name', :form => form) | |
19 | + task = CustomFormsPlugin::MembershipSurvey.create!(:form_id => form.id, :submission => {'name' => 'Jack'}, :target => person, :requestor => profile) | |
20 | + | |
21 | + assert_difference CustomFormsPlugin::Submission, :count, 1 do | |
22 | + task.finish | |
23 | + end | |
24 | + | |
25 | + submission = CustomFormsPlugin::Submission.last | |
26 | + assert_equal submission.answers.count, 1 | |
27 | + | |
28 | + answer = submission.answers.first | |
29 | + assert_equal answer.value, 'Jack' | |
30 | + end | |
31 | +end | ... | ... |
plugins/custom_forms/test/unit/custom_forms_plugin/select_field_test.rb
0 → 100644
... | ... | @@ -0,0 +1,13 @@ |
1 | +require File.dirname(__FILE__) + '/../../../../../test/test_helper' | |
2 | + | |
3 | +class CustomFormsPlugin::SelectFieldTest < ActiveSupport::TestCase | |
4 | + should 'validate presence of choices, multiple and list' do | |
5 | + select = CustomFormsPlugin::SelectField.new | |
6 | + select.valid? | |
7 | + assert select.errors.invalid?(:choices) | |
8 | + | |
9 | + select.choices = {'label' => 'value'} | |
10 | + select.valid? | |
11 | + assert !select.errors.invalid?(:choices) | |
12 | + end | |
13 | +end | ... | ... |
plugins/custom_forms/test/unit/custom_forms_plugin/submission_test.rb
0 → 100644
... | ... | @@ -0,0 +1,46 @@ |
1 | +require File.dirname(__FILE__) + '/../../../../../test/test_helper' | |
2 | + | |
3 | +class CustomFormsPlugin::SubmissionTest < ActiveSupport::TestCase | |
4 | + should 'validates presence of form' do | |
5 | + submission = CustomFormsPlugin::Submission.new | |
6 | + submission.valid? | |
7 | + assert submission.errors.invalid?(:form) | |
8 | + | |
9 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
10 | + submission.form = form | |
11 | + submission.valid? | |
12 | + assert !submission.errors.invalid?(:form) | |
13 | + end | |
14 | + | |
15 | + should 'belong to a profile' do | |
16 | + profile = fast_create(Profile) | |
17 | + submission = CustomFormsPlugin::Submission.new | |
18 | + submission.profile = profile | |
19 | + assert_equal profile, submission.profile | |
20 | + end | |
21 | + | |
22 | + should 'require presence of author name and email if profile is nil' do | |
23 | + submission = CustomFormsPlugin::Submission.new | |
24 | + submission.valid? | |
25 | + assert submission.errors.invalid?(:author_name) | |
26 | + assert submission.errors.invalid?(:author_email) | |
27 | + | |
28 | + submission.author_name = 'Jack Sparrow' | |
29 | + submission.author_email = 'jack@black-pearl.com' | |
30 | + submission.valid? | |
31 | + assert !submission.errors.invalid?(:author_name) | |
32 | + assert !submission.errors.invalid?(:author_email) | |
33 | + end | |
34 | + | |
35 | + should 'have answers' do | |
36 | + form = CustomFormsPlugin::Form.create!(:name => 'Free Software', :profile => fast_create(Profile)) | |
37 | + field = CustomFormsPlugin::Field.create!(:name => 'License', :form => form) | |
38 | + submission = CustomFormsPlugin::Submission.create!(:form => form, :profile => fast_create(Profile)) | |
39 | + a1 = CustomFormsPlugin::Answer.create!(:field => field, :submission => submission) | |
40 | + a2 = CustomFormsPlugin::Answer.create!(:field => field, :submission => submission) | |
41 | + | |
42 | + assert_includes submission.answers, a1 | |
43 | + assert_includes submission.answers, a2 | |
44 | + end | |
45 | +end | |
46 | + | ... | ... |
plugins/custom_forms/test/unit/ext/role_assingment_test.rb
0 → 100644
... | ... | @@ -0,0 +1,51 @@ |
1 | +require File.dirname(__FILE__) + '/../../../../../test/test_helper' | |
2 | + | |
3 | +class RoleAssignmentsTest < ActiveSupport::TestCase | |
4 | + should 'create membership_surveys on membership creation' do | |
5 | + environment = Environment.default | |
6 | + environment.enable_plugin(CustomFormsPlugin) | |
7 | + organization = fast_create(Organization) | |
8 | + person = fast_create(Person) | |
9 | + f1 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 1', :on_membership => true) | |
10 | + f2 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 2', :on_membership => true) | |
11 | + f3 = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form 3', :on_membership => false) | |
12 | + | |
13 | + assert_difference CustomFormsPlugin::MembershipSurvey, :count, 2 do | |
14 | + organization.add_member(person) | |
15 | + end | |
16 | + end | |
17 | + | |
18 | + should 'create membership_survey on membership creation with form accessible to members only' do | |
19 | + environment = Environment.default | |
20 | + environment.enable_plugin(CustomFormsPlugin) | |
21 | + organization = fast_create(Organization) | |
22 | + person = fast_create(Person) | |
23 | + form = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form', :on_membership => true, :access => 'associated') | |
24 | + | |
25 | + assert_difference CustomFormsPlugin::MembershipSurvey, :count, 1 do | |
26 | + organization.add_member(person) | |
27 | + end | |
28 | + end | |
29 | + | |
30 | + should 'cancel membership_surveys if membership is undone and task is active' do | |
31 | + environment = Environment.default | |
32 | + environment.enable_plugin(CustomFormsPlugin) | |
33 | + organization = fast_create(Organization) | |
34 | + person = fast_create(Person) | |
35 | + form = CustomFormsPlugin::Form.create!(:profile => organization, :name => 'Form', :on_membership => true) | |
36 | + organization.add_member(person) | |
37 | + | |
38 | + assert_difference CustomFormsPlugin::MembershipSurvey.pending, :count, -1 do | |
39 | + organization.remove_member(person) | |
40 | + end | |
41 | + | |
42 | + organization.add_member(person) | |
43 | + task = CustomFormsPlugin::MembershipSurvey.last | |
44 | + task.status = Task::Status::FINISHED | |
45 | + task.save! | |
46 | + assert_no_difference CustomFormsPlugin::MembershipSurvey.finished, :count do | |
47 | + organization.remove_member(person) | |
48 | + end | |
49 | + end | |
50 | +end | |
51 | + | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_edit_select.html.erb
0 → 100644
... | ... | @@ -0,0 +1,33 @@ |
1 | +<% elem_id = "edit-select-#{counter}" %> | |
2 | +<div id=<%= elem_id %> class='edit-information'> | |
3 | + <h1><%= _('Options') %></h1> | |
4 | + <table class='action-table' style='width: 420px'> | |
5 | + <tr> | |
6 | + <th style='width: 40%'><%= _('Name') %></th> | |
7 | + <th style='width: 40%'><%= _('Value') %></th> | |
8 | + <th style='width: 20%'><%= _('Actions') %></th> | |
9 | + </tr> | |
10 | + <% option_counter = 1 %> | |
11 | + <% (field.choices || {}).each do |name, value| %> | |
12 | + <%= render :partial => 'option', :locals => {:name => name, :value => value, :counter => counter, :option_counter => option_counter} %> | |
13 | + <% option_counter += 1 %> | |
14 | + <% end %> | |
15 | + <%= render :partial => 'empty_option', :locals => {:counter => counter, :option_counter => option_counter} %> | |
16 | + <tr class='new-item'> | |
17 | + <td colspan='3'><strong><%= link_to(_('NEW OPTION'), '#', :class => 'new-option', :field_id => counter)%></strong></td> | |
18 | + </tr> | |
19 | + </table> | |
20 | + | |
21 | + <h2><%= _('Type') %></h2> | |
22 | + <%#= labelled_check_box _('List'), "fields[#{counter}][list]", true, field.list %> | |
23 | + <%#= labelled_check_box _('Multiple'), "fields[#{counter}][multiple]", true, field.multiple %> | |
24 | + <%= labelled_radio_button 'Radio', "fields[#{counter}][kind]", 'radio', !field.multiple && !field.list %><br /> | |
25 | + <%= labelled_radio_button 'Check Box', "fields[#{counter}][kind]", 'check_box', field.multiple && !field.list %><br /> | |
26 | + <%= labelled_radio_button 'Select', "fields[#{counter}][kind]", 'select', !field.multiple && field.list %><br /> | |
27 | + <%= labelled_radio_button 'Multiple Select', "fields[#{counter}][kind]", 'multiple_select', field.multiple && field.list %><br /> | |
28 | + | |
29 | + <% button_bar do %> | |
30 | + <%= button :ok, _('Ok'), '#', :class => 'colorbox-ok-button', :div_id => elem_id %> | |
31 | + <% end %> | |
32 | +</div> | |
33 | + | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_edit_text.html.erb
0 → 100644
... | ... | @@ -0,0 +1,9 @@ |
1 | +<% elem_id = "edit-text-#{counter}" %> | |
2 | +<div id=<%= elem_id %> class='edit-information'> | |
3 | + <%= labelled_form_field _('Default value'), text_field("fields[#{counter}]", :default_value, :value => field.default_value) %> | |
4 | + | |
5 | + <% button_bar do %> | |
6 | + <%= button :ok, _('Ok'), '#', :class => 'colorbox-ok-button', :div_id => elem_id %> | |
7 | + <% end %> | |
8 | +</div> | |
9 | + | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_empty_field.html.erb
0 → 100644
... | ... | @@ -0,0 +1,10 @@ |
1 | +<tr id="empty-field" style='display: none' last_id=<%= counter %>> | |
2 | + <td style="text-align: left"><%= text_field "fields[#{counter}]", :name %></td> | |
3 | + <td><%= select "fields[#{counter}]", :type, type_options, :selected => type_for_options(field.class) %></td> | |
4 | + <td><%= check_box "fields[#{counter}]", :mandatory %></td> | |
5 | + <%= hidden_field "fields[#{counter}]", :form_id, :value => @form.id %> | |
6 | + <td class='actions'> | |
7 | + <%= button_without_text :edit, _('Edit'), '', :field_id => counter %> | |
8 | + <%= button_without_text :remove, _('Remove'), '#', :class => 'remove-field', :field_id => counter, :confirm => _('Are you sure you want to remove this field?') %> | |
9 | + </td> | |
10 | +</tr> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_empty_option.html.erb
0 → 100644
... | ... | @@ -0,0 +1,8 @@ |
1 | +<tr id=<%= "empty-option-#{counter}" %> option_id=<%= option_counter %> style="display: none;"> | |
2 | + <td><%= text_field_tag("fields[#{counter}][choices][#{option_counter}][name]") %></td> | |
3 | + <td><%= text_field_tag("fields[#{counter}][choices][#{option_counter}][value]") %></td> | |
4 | + <td class='actions'> | |
5 | + <%= button_without_text :remove, _('Remove'), '#', :class => 'remove-option', :field_id => counter, :option_id => option_counter, :confirm => _('Are you sure you want to remove this option?') %> | |
6 | + </td> | |
7 | +</tr> | |
8 | + | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_field.html.erb
0 → 100644
... | ... | @@ -0,0 +1,12 @@ |
1 | +<tr id=<%= "field-#{counter}" %>> | |
2 | + <td style="text-align: left"><%= text_field "fields[#{counter}]", :name, :value => field.name %></td> | |
3 | + <td><%= type_to_label(field.type) %></td> | |
4 | + <%= hidden_field "fields[#{counter}]", :type, :value => type_for_options(field.class) %> | |
5 | + <td><%= check_box "fields[#{counter}]", :mandatory, :checked => field.mandatory %></td> | |
6 | + <%= hidden_field "fields[#{counter}]", :real_id, :value => field.id %> | |
7 | + <%= hidden_field "fields[#{counter}]", :form_id, :value => @form.id %> | |
8 | + <td class='actions'> | |
9 | + <%= button_without_text :edit, _('Edit'), '#', :field_id => counter %> | |
10 | + <%= button_without_text :remove, _('Remove'), '#', :class => 'remove-field', :field_id => counter, :confirm => _('Are you sure you want to remove this field?') %> | |
11 | + </td> | |
12 | +</tr> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_form.html.erb
0 → 100644
... | ... | @@ -0,0 +1,54 @@ |
1 | +<% self.extend(CustomFormsPlugin::Helper) %> | |
2 | + | |
3 | +<%= error_messages_for :form %> | |
4 | + | |
5 | +<% form_for :form, @form do |f| %> | |
6 | + <%= required labelled_form_field _('Name'), f.text_field(:name) %> | |
7 | + <%= labelled_form_field(_('Período'), ( | |
8 | + date_range_field('form[begining]', 'form[ending]', @form.begining, @form.ending, | |
9 | + '%Y-%m-%d %H:%M', | |
10 | + { :time => true, :change_month => true, :change_year => true, | |
11 | + :date_format => 'yy-mm-dd', :time_format => 'hh:mm' }, | |
12 | + { :size => 14 }) | |
13 | + )) %> | |
14 | + <%= labelled_form_field _('Access'), f.select(:access, access_options(profile))%> | |
15 | + <% if profile.organization? %> | |
16 | + <%= labelled_form_field _('Triggered on membership'), f.check_box(:on_membership) %> | |
17 | + <% end %> | |
18 | + <%= labelled_form_field _('Description'), f.text_area(:description, :style => 'width: 100%') %> | |
19 | + | |
20 | + <h2><%= _('Fields') %></h2> | |
21 | + <table class="action-table" id='fields-table'> | |
22 | + <tr> | |
23 | + <th style='width: 40%'><%= _('Name') %></th> | |
24 | + <th style='width: 30%'><%= _('Type') %></th> | |
25 | + <th style='width: 10%'><%= _('Mandatory') %></th> | |
26 | + <th style='width: 20%'><%= _('Actions') %></th> | |
27 | + </tr> | |
28 | + <% counter = 1 %> | |
29 | + <% @fields.each do |field| %> | |
30 | + <%= render :partial => 'field', :locals => {:field => field, :counter => counter} %> | |
31 | + <% counter += 1 %> | |
32 | + <% end %> | |
33 | + <%= render :partial => 'empty_field', :locals => {:field => @empty_field, :counter => counter} %> | |
34 | + <tr class='new-item'> | |
35 | + <td colspan='5'><strong><%= link_to(_('NEW FIELD'), '#', :id => 'new-field')%></strong></td> | |
36 | + </tr> | |
37 | + </table> | |
38 | + | |
39 | + <% counter = 1 %> | |
40 | + <% @fields.each do |field| %> | |
41 | + <%= render :partial => 'edit_text', :locals => {:field => field, :counter => counter} %> | |
42 | + <%= render :partial => 'edit_select', :locals => {:field => field, :counter => counter} %> | |
43 | + <% counter += 1 %> | |
44 | + <% end %> | |
45 | + | |
46 | + <%= render :partial => 'edit_text', :locals => {:field => @empty_field, :counter => counter} %> | |
47 | + <%= render :partial => 'edit_select', :locals => {:field => @empty_field, :counter => counter} %> | |
48 | + | |
49 | + <% button_bar do %> | |
50 | + <%= submit_button :save, _('Save'), :cancel => {:action => 'index'}%> | |
51 | + <% end %> | |
52 | +<% end %> | |
53 | + | |
54 | +<%= javascript_include_tag '../plugins/custom_forms/field' %> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/_option.html.erb
0 → 100644
... | ... | @@ -0,0 +1,7 @@ |
1 | +<tr id=<%= "field-#{counter}-option-#{option_counter}" %> style="display: auto;"> | |
2 | + <td><%= text_field_tag("fields[#{counter}][choices][#{option_counter}][name]", name) %></td> | |
3 | + <td><%= text_field_tag("fields[#{counter}][choices][#{option_counter}][value]", value) %></td> | |
4 | + <td class='actions'> | |
5 | + <%= button_without_text :remove, _('Remove'), '#', :class => 'remove-option', :field_id => counter, :option_id => option_counter, :confirm => _('Are you sure you want to remove this option?') %> | |
6 | + </td> | |
7 | +</tr> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/create.html.erb
0 → 100644
plugins/custom_forms/views/custom_forms_plugin_myprofile/edit.html.erb
0 → 100644
plugins/custom_forms/views/custom_forms_plugin_myprofile/index.html.erb
0 → 100644
... | ... | @@ -0,0 +1,26 @@ |
1 | +<% self.extend CustomFormsPlugin::Helper %> | |
2 | + | |
3 | +<h1><%= _('Manage forms') %></h1> | |
4 | +<table class="action-table"> | |
5 | + <tr> | |
6 | + <th style='width: 40%'><%= _('Name') %></th> | |
7 | + <th style='width: 30%'><%= _('Period') %></th> | |
8 | + <th style='width: 10%'><%= _('Submissions') %></th> | |
9 | + <th style='width: 10%'><%= _('Access') %></th> | |
10 | + <th style='width: 10%'><%= _('Actions') %></th> | |
11 | + </tr> | |
12 | + <% @forms.each do |form| %> | |
13 | + <tr> | |
14 | + <td><%= form.name %></td> | |
15 | + <td><%= period_range(form) %></td> | |
16 | + <td><%= form.submissions.count > 0 ? link_to(form.submissions.count, {:action => 'submissions', :id => form.id}) : 0 %></td> | |
17 | + <td><%= access_text(form) %></td> | |
18 | + <td class="actions"> | |
19 | + <%= button_without_text :edit, _('Edit'), :action => 'edit', :id => form.id %> | |
20 | + <%= button_without_text :remove, _('Remove'), {:action => 'remove', :id => form.id}, :confirm => _('Are you sure you want to remove this form?') %></td> | |
21 | + </tr> | |
22 | + <% end %> | |
23 | + <tr id="new-item"> | |
24 | + <td colspan='5'><strong><%= link_to(_('NEW FORM'), :action => 'create')%></strong></td> | |
25 | + </tr> | |
26 | +</table> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/show_submission.html.erb
0 → 100644
... | ... | @@ -0,0 +1,10 @@ |
1 | +<h1><%= @form.name %></h1> | |
2 | +<p><%= @form.description %></p> | |
3 | + | |
4 | +<% fields_for :submission, @submission do |f| %> | |
5 | + <%= render :partial => 'shared/form_submission', :locals => {:f => f} %> | |
6 | +<% end %> | |
7 | + | |
8 | +<% button_bar do %> | |
9 | + <%= button :back, _('Back to submissions'), :action => 'submissions', :id => @form.id %> | |
10 | +<% end %> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_myprofile/submissions.html.erb
0 → 100644
... | ... | @@ -0,0 +1,25 @@ |
1 | +<% self.extend CustomFormsPlugin::Helper %> | |
2 | + | |
3 | +<h1><%= _('Submissions for %s') % @form.name %></h1> | |
4 | + | |
5 | +<% if @form.submissions.empty? %> | |
6 | + <%= _('There are no submissions for this form.') %> | |
7 | +<% else %> | |
8 | + <table class="action-table"> | |
9 | + <tr> | |
10 | + <th style='width: 50%'><%= _('Author') %></th> | |
11 | + <th style='width: 50%'><%= _('Time') %></th> | |
12 | + </tr> | |
13 | + <% @submissions.each do |submission| %> | |
14 | + <tr> | |
15 | + <% author = submission.profile.present? ? submission.profile.name : submission.author_name %> | |
16 | + <td><%= link_to(author, {:action => 'show_submission', :id => submission.id}) %></td> | |
17 | + <td><%= time_format(submission.created_at) %></td> | |
18 | + </tr> | |
19 | + <% end %> | |
20 | + </table> | |
21 | +<% end %> | |
22 | + | |
23 | +<% button_bar do %> | |
24 | + <%= button :back, _('Back to forms'), :action => 'index' %> | |
25 | +<% end %> | ... | ... |
plugins/custom_forms/views/custom_forms_plugin_profile/show.html.erb
0 → 100644
... | ... | @@ -0,0 +1,23 @@ |
1 | +<h1><%= @form.name %></h1> | |
2 | +<p><%= @form.description %></p> | |
3 | + | |
4 | +<% if @submission.id.nil? %> | |
5 | + <%= error_messages_for :submission %> | |
6 | + | |
7 | + <% form_for :submission, @submission do |f| %> | |
8 | + <% if !user %> | |
9 | + <%= required labelled_form_field _('Author name'), text_field_tag(:author_name, @submission.author_name) %> | |
10 | + <%= required labelled_form_field _('Author email'), text_field_tag(:author_email, @submission.author_email) %> | |
11 | + <% end %> | |
12 | + | |
13 | + <%= render :partial => 'shared/form_submission', :locals => {:f => f} %> | |
14 | + | |
15 | + <% button_bar do %> | |
16 | + <%= submit_button :save, _('Save'), :cancel => {:action => 'index'} %> | |
17 | + <% end %> | |
18 | + <% end %> | |
19 | +<% else %> | |
20 | + <% fields_for :submission, @submission do |f| %> | |
21 | + <%= render :partial => 'shared/form_submission', :locals => {:f => f} %> | |
22 | + <% end %> | |
23 | +<% end %> | ... | ... |
plugins/custom_forms/views/shared/_form_submission.html.erb
0 → 100644
plugins/custom_forms/views/tasks/_membership_survey_accept_details.html.erb
0 → 100644
... | ... | @@ -0,0 +1 @@ |
1 | +<%= f.object_name.to_s %> | ... | ... |
plugins/custom_forms/views/tasks/custom_forms_plugin/_membership_survey_accept_details.html.erb
0 → 100644
... | ... | @@ -0,0 +1,10 @@ |
1 | +<% @form = CustomFormsPlugin::Form.find(task.form_id) %> | |
2 | +<% @submission = CustomFormsPlugin::Submission.new(:form_id => @form.id, :profile_id => user.id) %> | |
3 | + | |
4 | +<h2><%= @form.name %></h2> | |
5 | +<p><%= @form.description %></p> | |
6 | + | |
7 | +<% f.fields_for :submission do |fi| %> | |
8 | + <%#= fi.error_messages_for :submission %> | |
9 | + <%= render :partial => 'shared/form_submission', :locals => {:f => fi} %> | |
10 | +<% end %> | ... | ... |
... | ... | @@ -0,0 +1,1530 @@ |
1 | +/* | |
2 | +* jQuery timepicker addon | |
3 | +* By: Trent Richardson [http://trentrichardson.com] | |
4 | +* Version 1.0.1 | |
5 | +* Last Modified: 07/01/2012 | |
6 | +* | |
7 | +* Copyright 2012 Trent Richardson | |
8 | +* You may use this project under MIT or GPL licenses. | |
9 | +* http://trentrichardson.com/Impromptu/GPL-LICENSE.txt | |
10 | +* http://trentrichardson.com/Impromptu/MIT-LICENSE.txt | |
11 | +* | |
12 | +* HERES THE CSS: | |
13 | +* .ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } | |
14 | +* .ui-timepicker-div dl { text-align: left; } | |
15 | +* .ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } | |
16 | +* .ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } | |
17 | +* .ui-timepicker-div td { font-size: 90%; } | |
18 | +* .ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } | |
19 | +*/ | |
20 | + | |
21 | +/*jslint evil: true, maxlen: 300, white: false, undef: false, nomen: false, onevar: false */ | |
22 | + | |
23 | +(function($) { | |
24 | + | |
25 | +// Prevent "Uncaught RangeError: Maximum call stack size exceeded" | |
26 | +$.ui.timepicker = $.ui.timepicker || {}; | |
27 | +if ($.ui.timepicker.version) { | |
28 | + return; | |
29 | +} | |
30 | + | |
31 | +$.extend($.ui, { timepicker: { version: "1.0.1" } }); | |
32 | + | |
33 | +/* Time picker manager. | |
34 | + Use the singleton instance of this class, $.timepicker, to interact with the time picker. | |
35 | + Settings for (groups of) time pickers are maintained in an instance object, | |
36 | + allowing multiple different settings on the same page. */ | |
37 | + | |
38 | +function Timepicker() { | |
39 | + this.regional = []; // Available regional settings, indexed by language code | |
40 | + this.regional[''] = { // Default regional settings | |
41 | + currentText: 'Now', | |
42 | + closeText: 'Done', | |
43 | + ampm: false, | |
44 | + amNames: ['AM', 'A'], | |
45 | + pmNames: ['PM', 'P'], | |
46 | + timeFormat: 'hh:mm tt', | |
47 | + timeSuffix: '', | |
48 | + timeOnlyTitle: 'Choose Time', | |
49 | + timeText: 'Time', | |
50 | + hourText: 'Hour', | |
51 | + minuteText: 'Minute', | |
52 | + secondText: 'Second', | |
53 | + millisecText: 'Millisecond', | |
54 | + timezoneText: 'Time Zone' | |
55 | + }; | |
56 | + this._defaults = { // Global defaults for all the datetime picker instances | |
57 | + showButtonPanel: true, | |
58 | + timeOnly: false, | |
59 | + showHour: true, | |
60 | + showMinute: true, | |
61 | + showSecond: false, | |
62 | + showMillisec: false, | |
63 | + showTimezone: false, | |
64 | + showTime: true, | |
65 | + stepHour: 1, | |
66 | + stepMinute: 1, | |
67 | + stepSecond: 1, | |
68 | + stepMillisec: 1, | |
69 | + hour: 0, | |
70 | + minute: 0, | |
71 | + second: 0, | |
72 | + millisec: 0, | |
73 | + timezone: null, | |
74 | + useLocalTimezone: false, | |
75 | + defaultTimezone: "+0000", | |
76 | + hourMin: 0, | |
77 | + minuteMin: 0, | |
78 | + secondMin: 0, | |
79 | + millisecMin: 0, | |
80 | + hourMax: 23, | |
81 | + minuteMax: 59, | |
82 | + secondMax: 59, | |
83 | + millisecMax: 999, | |
84 | + minDateTime: null, | |
85 | + maxDateTime: null, | |
86 | + onSelect: null, | |
87 | + hourGrid: 0, | |
88 | + minuteGrid: 0, | |
89 | + secondGrid: 0, | |
90 | + millisecGrid: 0, | |
91 | + alwaysSetTime: true, | |
92 | + separator: ' ', | |
93 | + altFieldTimeOnly: true, | |
94 | + showTimepicker: true, | |
95 | + timezoneIso8601: false, | |
96 | + timezoneList: null, | |
97 | + addSliderAccess: false, | |
98 | + sliderAccessArgs: null | |
99 | + }; | |
100 | + $.extend(this._defaults, this.regional['']); | |
101 | +} | |
102 | + | |
103 | +$.extend(Timepicker.prototype, { | |
104 | + $input: null, | |
105 | + $altInput: null, | |
106 | + $timeObj: null, | |
107 | + inst: null, | |
108 | + hour_slider: null, | |
109 | + minute_slider: null, | |
110 | + second_slider: null, | |
111 | + millisec_slider: null, | |
112 | + timezone_select: null, | |
113 | + hour: 0, | |
114 | + minute: 0, | |
115 | + second: 0, | |
116 | + millisec: 0, | |
117 | + timezone: null, | |
118 | + defaultTimezone: "+0000", | |
119 | + hourMinOriginal: null, | |
120 | + minuteMinOriginal: null, | |
121 | + secondMinOriginal: null, | |
122 | + millisecMinOriginal: null, | |
123 | + hourMaxOriginal: null, | |
124 | + minuteMaxOriginal: null, | |
125 | + secondMaxOriginal: null, | |
126 | + millisecMaxOriginal: null, | |
127 | + ampm: '', | |
128 | + formattedDate: '', | |
129 | + formattedTime: '', | |
130 | + formattedDateTime: '', | |
131 | + timezoneList: null, | |
132 | + | |
133 | + /* Override the default settings for all instances of the time picker. | |
134 | + @param settings object - the new settings to use as defaults (anonymous object) | |
135 | + @return the manager object */ | |
136 | + setDefaults: function(settings) { | |
137 | + extendRemove(this._defaults, settings || {}); | |
138 | + return this; | |
139 | + }, | |
140 | + | |
141 | + //######################################################################## | |
142 | + // Create a new Timepicker instance | |
143 | + //######################################################################## | |
144 | + _newInst: function($input, o) { | |
145 | + var tp_inst = new Timepicker(), | |
146 | + inlineSettings = {}; | |
147 | + | |
148 | + for (var attrName in this._defaults) { | |
149 | + var attrValue = $input.attr('time:' + attrName); | |
150 | + if (attrValue) { | |
151 | + try { | |
152 | + inlineSettings[attrName] = eval(attrValue); | |
153 | + } catch (err) { | |
154 | + inlineSettings[attrName] = attrValue; | |
155 | + } | |
156 | + } | |
157 | + } | |
158 | + tp_inst._defaults = $.extend({}, this._defaults, inlineSettings, o, { | |
159 | + beforeShow: function(input, dp_inst) { | |
160 | + if ($.isFunction(o.beforeShow)) { | |
161 | + return o.beforeShow(input, dp_inst, tp_inst); | |
162 | + } | |
163 | + }, | |
164 | + onChangeMonthYear: function(year, month, dp_inst) { | |
165 | + // Update the time as well : this prevents the time from disappearing from the $input field. | |
166 | + tp_inst._updateDateTime(dp_inst); | |
167 | + if ($.isFunction(o.onChangeMonthYear)) { | |
168 | + o.onChangeMonthYear.call($input[0], year, month, dp_inst, tp_inst); | |
169 | + } | |
170 | + }, | |
171 | + onClose: function(dateText, dp_inst) { | |
172 | + if (tp_inst.timeDefined === true && $input.val() !== '') { | |
173 | + tp_inst._updateDateTime(dp_inst); | |
174 | + } | |
175 | + if ($.isFunction(o.onClose)) { | |
176 | + o.onClose.call($input[0], dateText, dp_inst, tp_inst); | |
177 | + } | |
178 | + }, | |
179 | + timepicker: tp_inst // add timepicker as a property of datepicker: $.datepicker._get(dp_inst, 'timepicker'); | |
180 | + }); | |
181 | + tp_inst.amNames = $.map(tp_inst._defaults.amNames, function(val) { return val.toUpperCase(); }); | |
182 | + tp_inst.pmNames = $.map(tp_inst._defaults.pmNames, function(val) { return val.toUpperCase(); }); | |
183 | + | |
184 | + if (tp_inst._defaults.timezoneList === null) { | |
185 | + var timezoneList = []; | |
186 | + for (var i = -11; i <= 12; i++) { | |
187 | + timezoneList.push((i >= 0 ? '+' : '-') + ('0' + Math.abs(i).toString()).slice(-2) + '00'); | |
188 | + } | |
189 | + if (tp_inst._defaults.timezoneIso8601) { | |
190 | + timezoneList = $.map(timezoneList, function(val) { | |
191 | + return val == '+0000' ? 'Z' : (val.substring(0, 3) + ':' + val.substring(3)); | |
192 | + }); | |
193 | + } | |
194 | + tp_inst._defaults.timezoneList = timezoneList; | |
195 | + } | |
196 | + | |
197 | + tp_inst.timezone = tp_inst._defaults.timezone; | |
198 | + tp_inst.hour = tp_inst._defaults.hour; | |
199 | + tp_inst.minute = tp_inst._defaults.minute; | |
200 | + tp_inst.second = tp_inst._defaults.second; | |
201 | + tp_inst.millisec = tp_inst._defaults.millisec; | |
202 | + tp_inst.ampm = ''; | |
203 | + tp_inst.$input = $input; | |
204 | + | |
205 | + if (o.altField) { | |
206 | + tp_inst.$altInput = $(o.altField) | |
207 | + .css({ cursor: 'pointer' }) | |
208 | + .focus(function(){ $input.trigger("focus"); }); | |
209 | + } | |
210 | + | |
211 | + if(tp_inst._defaults.minDate===0 || tp_inst._defaults.minDateTime===0) | |
212 | + { | |
213 | + tp_inst._defaults.minDate=new Date(); | |
214 | + } | |
215 | + if(tp_inst._defaults.maxDate===0 || tp_inst._defaults.maxDateTime===0) | |
216 | + { | |
217 | + tp_inst._defaults.maxDate=new Date(); | |
218 | + } | |
219 | + | |
220 | + // datepicker needs minDate/maxDate, timepicker needs minDateTime/maxDateTime.. | |
221 | + if(tp_inst._defaults.minDate !== undefined && tp_inst._defaults.minDate instanceof Date) { | |
222 | + tp_inst._defaults.minDateTime = new Date(tp_inst._defaults.minDate.getTime()); | |
223 | + } | |
224 | + if(tp_inst._defaults.minDateTime !== undefined && tp_inst._defaults.minDateTime instanceof Date) { | |
225 | + tp_inst._defaults.minDate = new Date(tp_inst._defaults.minDateTime.getTime()); | |
226 | + } | |
227 | + if(tp_inst._defaults.maxDate !== undefined && tp_inst._defaults.maxDate instanceof Date) { | |
228 | + tp_inst._defaults.maxDateTime = new Date(tp_inst._defaults.maxDate.getTime()); | |
229 | + } | |
230 | + if(tp_inst._defaults.maxDateTime !== undefined && tp_inst._defaults.maxDateTime instanceof Date) { | |
231 | + tp_inst._defaults.maxDate = new Date(tp_inst._defaults.maxDateTime.getTime()); | |
232 | + } | |
233 | + return tp_inst; | |
234 | + }, | |
235 | + | |
236 | + //######################################################################## | |
237 | + // add our sliders to the calendar | |
238 | + //######################################################################## | |
239 | + _addTimePicker: function(dp_inst) { | |
240 | + var currDT = (this.$altInput && this._defaults.altFieldTimeOnly) ? | |
241 | + this.$input.val() + ' ' + this.$altInput.val() : | |
242 | + this.$input.val(); | |
243 | + | |
244 | + this.timeDefined = this._parseTime(currDT); | |
245 | + this._limitMinMaxDateTime(dp_inst, false); | |
246 | + this._injectTimePicker(); | |
247 | + }, | |
248 | + | |
249 | + //######################################################################## | |
250 | + // parse the time string from input value or _setTime | |
251 | + //######################################################################## | |
252 | + _parseTime: function(timeString, withDate) { | |
253 | + if (!this.inst) { | |
254 | + this.inst = $.datepicker._getInst(this.$input[0]); | |
255 | + } | |
256 | + | |
257 | + if (withDate || !this._defaults.timeOnly) | |
258 | + { | |
259 | + var dp_dateFormat = $.datepicker._get(this.inst, 'dateFormat'); | |
260 | + try { | |
261 | + var parseRes = parseDateTimeInternal(dp_dateFormat, this._defaults.timeFormat, timeString, $.datepicker._getFormatConfig(this.inst), this._defaults); | |
262 | + if (!parseRes.timeObj) { return false; } | |
263 | + $.extend(this, parseRes.timeObj); | |
264 | + } catch (err) | |
265 | + { | |
266 | + return false; | |
267 | + } | |
268 | + return true; | |
269 | + } | |
270 | + else | |
271 | + { | |
272 | + var timeObj = $.datepicker.parseTime(this._defaults.timeFormat, timeString, this._defaults); | |
273 | + if(!timeObj) { return false; } | |
274 | + $.extend(this, timeObj); | |
275 | + return true; | |
276 | + } | |
277 | + }, | |
278 | + | |
279 | + //######################################################################## | |
280 | + // generate and inject html for timepicker into ui datepicker | |
281 | + //######################################################################## | |
282 | + _injectTimePicker: function() { | |
283 | + var $dp = this.inst.dpDiv, | |
284 | + o = this._defaults, | |
285 | + tp_inst = this, | |
286 | + // Added by Peter Medeiros: | |
287 | + // - Figure out what the hour/minute/second max should be based on the step values. | |
288 | + // - Example: if stepMinute is 15, then minMax is 45. | |
289 | + hourMax = parseInt((o.hourMax - ((o.hourMax - o.hourMin) % o.stepHour)) ,10), | |
290 | + minMax = parseInt((o.minuteMax - ((o.minuteMax - o.minuteMin) % o.stepMinute)) ,10), | |
291 | + secMax = parseInt((o.secondMax - ((o.secondMax - o.secondMin) % o.stepSecond)) ,10), | |
292 | + millisecMax = parseInt((o.millisecMax - ((o.millisecMax - o.millisecMin) % o.stepMillisec)) ,10), | |
293 | + dp_id = this.inst.id.toString().replace(/([^A-Za-z0-9_])/g, ''); | |
294 | + | |
295 | + // Prevent displaying twice | |
296 | + //if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0) { | |
297 | + if ($dp.find("div#ui-timepicker-div-"+ dp_id).length === 0 && o.showTimepicker) { | |
298 | + var noDisplay = ' style="display:none;"', | |
299 | + html = '<div class="ui-timepicker-div" id="ui-timepicker-div-' + dp_id + '"><dl>' + | |
300 | + '<dt class="ui_tpicker_time_label" id="ui_tpicker_time_label_' + dp_id + '"' + | |
301 | + ((o.showTime) ? '' : noDisplay) + '>' + o.timeText + '</dt>' + | |
302 | + '<dd class="ui_tpicker_time" id="ui_tpicker_time_' + dp_id + '"' + | |
303 | + ((o.showTime) ? '' : noDisplay) + '></dd>' + | |
304 | + '<dt class="ui_tpicker_hour_label" id="ui_tpicker_hour_label_' + dp_id + '"' + | |
305 | + ((o.showHour) ? '' : noDisplay) + '>' + o.hourText + '</dt>', | |
306 | + hourGridSize = 0, | |
307 | + minuteGridSize = 0, | |
308 | + secondGridSize = 0, | |
309 | + millisecGridSize = 0, | |
310 | + size = null; | |
311 | + | |
312 | + // Hours | |
313 | + html += '<dd class="ui_tpicker_hour"><div id="ui_tpicker_hour_' + dp_id + '"' + | |
314 | + ((o.showHour) ? '' : noDisplay) + '></div>'; | |
315 | + if (o.showHour && o.hourGrid > 0) { | |
316 | + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; | |
317 | + | |
318 | + for (var h = o.hourMin; h <= hourMax; h += parseInt(o.hourGrid,10)) { | |
319 | + hourGridSize++; | |
320 | + var tmph = (o.ampm && h > 12) ? h-12 : h; | |
321 | + if (tmph < 10) { tmph = '0' + tmph; } | |
322 | + if (o.ampm) { | |
323 | + if (h === 0) { | |
324 | + tmph = 12 +'a'; | |
325 | + } else { | |
326 | + if (h < 12) { tmph += 'a'; } | |
327 | + else { tmph += 'p'; } | |
328 | + } | |
329 | + } | |
330 | + html += '<td>' + tmph + '</td>'; | |
331 | + } | |
332 | + | |
333 | + html += '</tr></table></div>'; | |
334 | + } | |
335 | + html += '</dd>'; | |
336 | + | |
337 | + // Minutes | |
338 | + html += '<dt class="ui_tpicker_minute_label" id="ui_tpicker_minute_label_' + dp_id + '"' + | |
339 | + ((o.showMinute) ? '' : noDisplay) + '>' + o.minuteText + '</dt>'+ | |
340 | + '<dd class="ui_tpicker_minute"><div id="ui_tpicker_minute_' + dp_id + '"' + | |
341 | + ((o.showMinute) ? '' : noDisplay) + '></div>'; | |
342 | + | |
343 | + if (o.showMinute && o.minuteGrid > 0) { | |
344 | + html += '<div style="padding-left: 1px"><table class="ui-tpicker-grid-label"><tr>'; | |
345 | + | |
346 | + for (var m = o.minuteMin; m <= minMax; m += parseInt(o.minuteGrid,10)) { | |
347 | + minuteGridSize++; | |
348 | + html += '<td>' + ((m < 10) ? '0' : '') + m + '</td>'; | |
349 | + } | |
350 | + | |
351 | + html += '</tr></table></div>'; | |
352 | + } | |
353 | + html += '</dd>'; | |
354 | + | |
355 | + // Seconds | |
356 | + html += '<dt class="ui_tpicker_second_label" id="ui_tpicker_second_label_' + dp_id + '"' + | |
357 | + ((o.showSecond) ? '' : noDisplay) + '>' + o.secondText + '</dt>'+ | |
358 | + '<dd class="ui_tpicker_second"><div id="ui_tpicker_second_' + dp_id + '"'+ | |
359 | + ((o.showSecond) ? '' : noDisplay) + '></div>'; | |
360 | + | |
361 | + if (o.showSecond && o.secondGrid > 0) { | |
362 | + html += '<div style="padding-left: 1px"><table><tr>'; | |
363 | + | |
364 | + for (var s = o.secondMin; s <= secMax; s += parseInt(o.secondGrid,10)) { | |
365 | + secondGridSize++; | |
366 | + html += '<td>' + ((s < 10) ? '0' : '') + s + '</td>'; | |
367 | + } | |
368 | + | |
369 | + html += '</tr></table></div>'; | |
370 | + } | |
371 | + html += '</dd>'; | |
372 | + | |
373 | + // Milliseconds | |
374 | + html += '<dt class="ui_tpicker_millisec_label" id="ui_tpicker_millisec_label_' + dp_id + '"' + | |
375 | + ((o.showMillisec) ? '' : noDisplay) + '>' + o.millisecText + '</dt>'+ | |
376 | + '<dd class="ui_tpicker_millisec"><div id="ui_tpicker_millisec_' + dp_id + '"'+ | |
377 | + ((o.showMillisec) ? '' : noDisplay) + '></div>'; | |
378 | + | |
379 | + if (o.showMillisec && o.millisecGrid > 0) { | |
380 | + html += '<div style="padding-left: 1px"><table><tr>'; | |
381 | + | |
382 | + for (var l = o.millisecMin; l <= millisecMax; l += parseInt(o.millisecGrid,10)) { | |
383 | + millisecGridSize++; | |
384 | + html += '<td>' + ((l < 10) ? '0' : '') + l + '</td>'; | |
385 | + } | |
386 | + | |
387 | + html += '</tr></table></div>'; | |
388 | + } | |
389 | + html += '</dd>'; | |
390 | + | |
391 | + // Timezone | |
392 | + html += '<dt class="ui_tpicker_timezone_label" id="ui_tpicker_timezone_label_' + dp_id + '"' + | |
393 | + ((o.showTimezone) ? '' : noDisplay) + '>' + o.timezoneText + '</dt>'; | |
394 | + html += '<dd class="ui_tpicker_timezone" id="ui_tpicker_timezone_' + dp_id + '"' + | |
395 | + ((o.showTimezone) ? '' : noDisplay) + '></dd>'; | |
396 | + | |
397 | + html += '</dl></div>'; | |
398 | + var $tp = $(html); | |
399 | + | |
400 | + // if we only want time picker... | |
401 | + if (o.timeOnly === true) { | |
402 | + $tp.prepend( | |
403 | + '<div class="ui-widget-header ui-helper-clearfix ui-corner-all">' + | |
404 | + '<div class="ui-datepicker-title">' + o.timeOnlyTitle + '</div>' + | |
405 | + '</div>'); | |
406 | + $dp.find('.ui-datepicker-header, .ui-datepicker-calendar').hide(); | |
407 | + } | |
408 | + | |
409 | + this.hour_slider = $tp.find('#ui_tpicker_hour_'+ dp_id).slider({ | |
410 | + orientation: "horizontal", | |
411 | + value: this.hour, | |
412 | + min: o.hourMin, | |
413 | + max: hourMax, | |
414 | + step: o.stepHour, | |
415 | + slide: function(event, ui) { | |
416 | + tp_inst.hour_slider.slider( "option", "value", ui.value); | |
417 | + tp_inst._onTimeChange(); | |
418 | + } | |
419 | + }); | |
420 | + | |
421 | + | |
422 | + // Updated by Peter Medeiros: | |
423 | + // - Pass in Event and UI instance into slide function | |
424 | + this.minute_slider = $tp.find('#ui_tpicker_minute_'+ dp_id).slider({ | |
425 | + orientation: "horizontal", | |
426 | + value: this.minute, | |
427 | + min: o.minuteMin, | |
428 | + max: minMax, | |
429 | + step: o.stepMinute, | |
430 | + slide: function(event, ui) { | |
431 | + tp_inst.minute_slider.slider( "option", "value", ui.value); | |
432 | + tp_inst._onTimeChange(); | |
433 | + } | |
434 | + }); | |
435 | + | |
436 | + this.second_slider = $tp.find('#ui_tpicker_second_'+ dp_id).slider({ | |
437 | + orientation: "horizontal", | |
438 | + value: this.second, | |
439 | + min: o.secondMin, | |
440 | + max: secMax, | |
441 | + step: o.stepSecond, | |
442 | + slide: function(event, ui) { | |
443 | + tp_inst.second_slider.slider( "option", "value", ui.value); | |
444 | + tp_inst._onTimeChange(); | |
445 | + } | |
446 | + }); | |
447 | + | |
448 | + this.millisec_slider = $tp.find('#ui_tpicker_millisec_'+ dp_id).slider({ | |
449 | + orientation: "horizontal", | |
450 | + value: this.millisec, | |
451 | + min: o.millisecMin, | |
452 | + max: millisecMax, | |
453 | + step: o.stepMillisec, | |
454 | + slide: function(event, ui) { | |
455 | + tp_inst.millisec_slider.slider( "option", "value", ui.value); | |
456 | + tp_inst._onTimeChange(); | |
457 | + } | |
458 | + }); | |
459 | + | |
460 | + this.timezone_select = $tp.find('#ui_tpicker_timezone_'+ dp_id).append('<select></select>').find("select"); | |
461 | + $.fn.append.apply(this.timezone_select, | |
462 | + $.map(o.timezoneList, function(val, idx) { | |
463 | + return $("<option />") | |
464 | + .val(typeof val == "object" ? val.value : val) | |
465 | + .text(typeof val == "object" ? val.label : val); | |
466 | + }) | |
467 | + ); | |
468 | + if (typeof(this.timezone) != "undefined" && this.timezone !== null && this.timezone !== "") { | |
469 | + var local_date = new Date(this.inst.selectedYear, this.inst.selectedMonth, this.inst.selectedDay, 12); | |
470 | + var local_timezone = timeZoneString(local_date); | |
471 | + if (local_timezone == this.timezone) { | |
472 | + selectLocalTimeZone(tp_inst); | |
473 | + } else { | |
474 | + this.timezone_select.val(this.timezone); | |
475 | + } | |
476 | + } else { | |
477 | + if (typeof(this.hour) != "undefined" && this.hour !== null && this.hour !== "") { | |
478 | + this.timezone_select.val(o.defaultTimezone); | |
479 | + } else { | |
480 | + selectLocalTimeZone(tp_inst); | |
481 | + } | |
482 | + } | |
483 | + this.timezone_select.change(function() { | |
484 | + tp_inst._defaults.useLocalTimezone = false; | |
485 | + tp_inst._onTimeChange(); | |
486 | + }); | |
487 | + | |
488 | + // Add grid functionality | |
489 | + if (o.showHour && o.hourGrid > 0) { | |
490 | + size = 100 * hourGridSize * o.hourGrid / (hourMax - o.hourMin); | |
491 | + | |
492 | + $tp.find(".ui_tpicker_hour table").css({ | |
493 | + width: size + "%", | |
494 | + marginLeft: (size / (-2 * hourGridSize)) + "%", | |
495 | + borderCollapse: 'collapse' | |
496 | + }).find("td").each( function(index) { | |
497 | + $(this).click(function() { | |
498 | + var h = $(this).html(); | |
499 | + if(o.ampm) { | |
500 | + var ap = h.substring(2).toLowerCase(), | |
501 | + aph = parseInt(h.substring(0,2), 10); | |
502 | + if (ap == 'a') { | |
503 | + if (aph == 12) { h = 0; } | |
504 | + else { h = aph; } | |
505 | + } else if (aph == 12) { h = 12; } | |
506 | + else { h = aph + 12; } | |
507 | + } | |
508 | + tp_inst.hour_slider.slider("option", "value", h); | |
509 | + tp_inst._onTimeChange(); | |
510 | + tp_inst._onSelectHandler(); | |
511 | + }).css({ | |
512 | + cursor: 'pointer', | |
513 | + width: (100 / hourGridSize) + '%', | |
514 | + textAlign: 'center', | |
515 | + overflow: 'hidden' | |
516 | + }); | |
517 | + }); | |
518 | + } | |
519 | + | |
520 | + if (o.showMinute && o.minuteGrid > 0) { | |
521 | + size = 100 * minuteGridSize * o.minuteGrid / (minMax - o.minuteMin); | |
522 | + $tp.find(".ui_tpicker_minute table").css({ | |
523 | + width: size + "%", | |
524 | + marginLeft: (size / (-2 * minuteGridSize)) + "%", | |
525 | + borderCollapse: 'collapse' | |
526 | + }).find("td").each(function(index) { | |
527 | + $(this).click(function() { | |
528 | + tp_inst.minute_slider.slider("option", "value", $(this).html()); | |
529 | + tp_inst._onTimeChange(); | |
530 | + tp_inst._onSelectHandler(); | |
531 | + }).css({ | |
532 | + cursor: 'pointer', | |
533 | + width: (100 / minuteGridSize) + '%', | |
534 | + textAlign: 'center', | |
535 | + overflow: 'hidden' | |
536 | + }); | |
537 | + }); | |
538 | + } | |
539 | + | |
540 | + if (o.showSecond && o.secondGrid > 0) { | |
541 | + $tp.find(".ui_tpicker_second table").css({ | |
542 | + width: size + "%", | |
543 | + marginLeft: (size / (-2 * secondGridSize)) + "%", | |
544 | + borderCollapse: 'collapse' | |
545 | + }).find("td").each(function(index) { | |
546 | + $(this).click(function() { | |
547 | + tp_inst.second_slider.slider("option", "value", $(this).html()); | |
548 | + tp_inst._onTimeChange(); | |
549 | + tp_inst._onSelectHandler(); | |
550 | + }).css({ | |
551 | + cursor: 'pointer', | |
552 | + width: (100 / secondGridSize) + '%', | |
553 | + textAlign: 'center', | |
554 | + overflow: 'hidden' | |
555 | + }); | |
556 | + }); | |
557 | + } | |
558 | + | |
559 | + if (o.showMillisec && o.millisecGrid > 0) { | |
560 | + $tp.find(".ui_tpicker_millisec table").css({ | |
561 | + width: size + "%", | |
562 | + marginLeft: (size / (-2 * millisecGridSize)) + "%", | |
563 | + borderCollapse: 'collapse' | |
564 | + }).find("td").each(function(index) { | |
565 | + $(this).click(function() { | |
566 | + tp_inst.millisec_slider.slider("option", "value", $(this).html()); | |
567 | + tp_inst._onTimeChange(); | |
568 | + tp_inst._onSelectHandler(); | |
569 | + }).css({ | |
570 | + cursor: 'pointer', | |
571 | + width: (100 / millisecGridSize) + '%', | |
572 | + textAlign: 'center', | |
573 | + overflow: 'hidden' | |
574 | + }); | |
575 | + }); | |
576 | + } | |
577 | + | |
578 | + var $buttonPanel = $dp.find('.ui-datepicker-buttonpane'); | |
579 | + if ($buttonPanel.length) { $buttonPanel.before($tp); } | |
580 | + else { $dp.append($tp); } | |
581 | + | |
582 | + this.$timeObj = $tp.find('#ui_tpicker_time_'+ dp_id); | |
583 | + | |
584 | + if (this.inst !== null) { | |
585 | + var timeDefined = this.timeDefined; | |
586 | + this._onTimeChange(); | |
587 | + this.timeDefined = timeDefined; | |
588 | + } | |
589 | + | |
590 | + //Emulate datepicker onSelect behavior. Call on slidestop. | |
591 | + var onSelectDelegate = function() { | |
592 | + tp_inst._onSelectHandler(); | |
593 | + }; | |
594 | + this.hour_slider.bind('slidestop',onSelectDelegate); | |
595 | + this.minute_slider.bind('slidestop',onSelectDelegate); | |
596 | + this.second_slider.bind('slidestop',onSelectDelegate); | |
597 | + this.millisec_slider.bind('slidestop',onSelectDelegate); | |
598 | + | |
599 | + // slideAccess integration: http://trentrichardson.com/2011/11/11/jquery-ui-sliders-and-touch-accessibility/ | |
600 | + if (this._defaults.addSliderAccess){ | |
601 | + var sliderAccessArgs = this._defaults.sliderAccessArgs; | |
602 | + setTimeout(function(){ // fix for inline mode | |
603 | + if($tp.find('.ui-slider-access').length === 0){ | |
604 | + $tp.find('.ui-slider:visible').sliderAccess(sliderAccessArgs); | |
605 | + | |
606 | + // fix any grids since sliders are shorter | |
607 | + var sliderAccessWidth = $tp.find('.ui-slider-access:eq(0)').outerWidth(true); | |
608 | + if(sliderAccessWidth){ | |
609 | + $tp.find('table:visible').each(function(){ | |
610 | + var $g = $(this), | |
611 | + oldWidth = $g.outerWidth(), | |
612 | + oldMarginLeft = $g.css('marginLeft').toString().replace('%',''), | |
613 | + newWidth = oldWidth - sliderAccessWidth, | |
614 | + newMarginLeft = ((oldMarginLeft * newWidth)/oldWidth) + '%'; | |
615 | + | |
616 | + $g.css({ width: newWidth, marginLeft: newMarginLeft }); | |
617 | + }); | |
618 | + } | |
619 | + } | |
620 | + },0); | |
621 | + } | |
622 | + // end slideAccess integration | |
623 | + | |
624 | + } | |
625 | + }, | |
626 | + | |
627 | + //######################################################################## | |
628 | + // This function tries to limit the ability to go outside the | |
629 | + // min/max date range | |
630 | + //######################################################################## | |
631 | + _limitMinMaxDateTime: function(dp_inst, adjustSliders){ | |
632 | + var o = this._defaults, | |
633 | + dp_date = new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay); | |
634 | + | |
635 | + if(!this._defaults.showTimepicker) { return; } // No time so nothing to check here | |
636 | + | |
637 | + if($.datepicker._get(dp_inst, 'minDateTime') !== null && $.datepicker._get(dp_inst, 'minDateTime') !== undefined && dp_date){ | |
638 | + var minDateTime = $.datepicker._get(dp_inst, 'minDateTime'), | |
639 | + minDateTimeDate = new Date(minDateTime.getFullYear(), minDateTime.getMonth(), minDateTime.getDate(), 0, 0, 0, 0); | |
640 | + | |
641 | + if(this.hourMinOriginal === null || this.minuteMinOriginal === null || this.secondMinOriginal === null || this.millisecMinOriginal === null){ | |
642 | + this.hourMinOriginal = o.hourMin; | |
643 | + this.minuteMinOriginal = o.minuteMin; | |
644 | + this.secondMinOriginal = o.secondMin; | |
645 | + this.millisecMinOriginal = o.millisecMin; | |
646 | + } | |
647 | + | |
648 | + if(dp_inst.settings.timeOnly || minDateTimeDate.getTime() == dp_date.getTime()) { | |
649 | + this._defaults.hourMin = minDateTime.getHours(); | |
650 | + if (this.hour <= this._defaults.hourMin) { | |
651 | + this.hour = this._defaults.hourMin; | |
652 | + this._defaults.minuteMin = minDateTime.getMinutes(); | |
653 | + if (this.minute <= this._defaults.minuteMin) { | |
654 | + this.minute = this._defaults.minuteMin; | |
655 | + this._defaults.secondMin = minDateTime.getSeconds(); | |
656 | + } else if (this.second <= this._defaults.secondMin){ | |
657 | + this.second = this._defaults.secondMin; | |
658 | + this._defaults.millisecMin = minDateTime.getMilliseconds(); | |
659 | + } else { | |
660 | + if(this.millisec < this._defaults.millisecMin) { | |
661 | + this.millisec = this._defaults.millisecMin; | |
662 | + } | |
663 | + this._defaults.millisecMin = this.millisecMinOriginal; | |
664 | + } | |
665 | + } else { | |
666 | + this._defaults.minuteMin = this.minuteMinOriginal; | |
667 | + this._defaults.secondMin = this.secondMinOriginal; | |
668 | + this._defaults.millisecMin = this.millisecMinOriginal; | |
669 | + } | |
670 | + }else{ | |
671 | + this._defaults.hourMin = this.hourMinOriginal; | |
672 | + this._defaults.minuteMin = this.minuteMinOriginal; | |
673 | + this._defaults.secondMin = this.secondMinOriginal; | |
674 | + this._defaults.millisecMin = this.millisecMinOriginal; | |
675 | + } | |
676 | + } | |
677 | + | |
678 | + if($.datepicker._get(dp_inst, 'maxDateTime') !== null && $.datepicker._get(dp_inst, 'maxDateTime') !== undefined && dp_date){ | |
679 | + var maxDateTime = $.datepicker._get(dp_inst, 'maxDateTime'), | |
680 | + maxDateTimeDate = new Date(maxDateTime.getFullYear(), maxDateTime.getMonth(), maxDateTime.getDate(), 0, 0, 0, 0); | |
681 | + | |
682 | + if(this.hourMaxOriginal === null || this.minuteMaxOriginal === null || this.secondMaxOriginal === null){ | |
683 | + this.hourMaxOriginal = o.hourMax; | |
684 | + this.minuteMaxOriginal = o.minuteMax; | |
685 | + this.secondMaxOriginal = o.secondMax; | |
686 | + this.millisecMaxOriginal = o.millisecMax; | |
687 | + } | |
688 | + | |
689 | + if(dp_inst.settings.timeOnly || maxDateTimeDate.getTime() == dp_date.getTime()){ | |
690 | + this._defaults.hourMax = maxDateTime.getHours(); | |
691 | + if (this.hour >= this._defaults.hourMax) { | |
692 | + this.hour = this._defaults.hourMax; | |
693 | + this._defaults.minuteMax = maxDateTime.getMinutes(); | |
694 | + if (this.minute >= this._defaults.minuteMax) { | |
695 | + this.minute = this._defaults.minuteMax; | |
696 | + this._defaults.secondMax = maxDateTime.getSeconds(); | |
697 | + } else if (this.second >= this._defaults.secondMax) { | |
698 | + this.second = this._defaults.secondMax; | |
699 | + this._defaults.millisecMax = maxDateTime.getMilliseconds(); | |
700 | + } else { | |
701 | + if(this.millisec > this._defaults.millisecMax) { this.millisec = this._defaults.millisecMax; } | |
702 | + this._defaults.millisecMax = this.millisecMaxOriginal; | |
703 | + } | |
704 | + } else { | |
705 | + this._defaults.minuteMax = this.minuteMaxOriginal; | |
706 | + this._defaults.secondMax = this.secondMaxOriginal; | |
707 | + this._defaults.millisecMax = this.millisecMaxOriginal; | |
708 | + } | |
709 | + }else{ | |
710 | + this._defaults.hourMax = this.hourMaxOriginal; | |
711 | + this._defaults.minuteMax = this.minuteMaxOriginal; | |
712 | + this._defaults.secondMax = this.secondMaxOriginal; | |
713 | + this._defaults.millisecMax = this.millisecMaxOriginal; | |
714 | + } | |
715 | + } | |
716 | + | |
717 | + if(adjustSliders !== undefined && adjustSliders === true){ | |
718 | + var hourMax = parseInt((this._defaults.hourMax - ((this._defaults.hourMax - this._defaults.hourMin) % this._defaults.stepHour)) ,10), | |
719 | + minMax = parseInt((this._defaults.minuteMax - ((this._defaults.minuteMax - this._defaults.minuteMin) % this._defaults.stepMinute)) ,10), | |
720 | + secMax = parseInt((this._defaults.secondMax - ((this._defaults.secondMax - this._defaults.secondMin) % this._defaults.stepSecond)) ,10), | |
721 | + millisecMax = parseInt((this._defaults.millisecMax - ((this._defaults.millisecMax - this._defaults.millisecMin) % this._defaults.stepMillisec)) ,10); | |
722 | + | |
723 | + if(this.hour_slider) { | |
724 | + this.hour_slider.slider("option", { min: this._defaults.hourMin, max: hourMax }).slider('value', this.hour); | |
725 | + } | |
726 | + if(this.minute_slider) { | |
727 | + this.minute_slider.slider("option", { min: this._defaults.minuteMin, max: minMax }).slider('value', this.minute); | |
728 | + } | |
729 | + if(this.second_slider){ | |
730 | + this.second_slider.slider("option", { min: this._defaults.secondMin, max: secMax }).slider('value', this.second); | |
731 | + } | |
732 | + if(this.millisec_slider) { | |
733 | + this.millisec_slider.slider("option", { min: this._defaults.millisecMin, max: millisecMax }).slider('value', this.millisec); | |
734 | + } | |
735 | + } | |
736 | + | |
737 | + }, | |
738 | + | |
739 | + | |
740 | + //######################################################################## | |
741 | + // when a slider moves, set the internal time... | |
742 | + // on time change is also called when the time is updated in the text field | |
743 | + //######################################################################## | |
744 | + _onTimeChange: function() { | |
745 | + var hour = (this.hour_slider) ? this.hour_slider.slider('value') : false, | |
746 | + minute = (this.minute_slider) ? this.minute_slider.slider('value') : false, | |
747 | + second = (this.second_slider) ? this.second_slider.slider('value') : false, | |
748 | + millisec = (this.millisec_slider) ? this.millisec_slider.slider('value') : false, | |
749 | + timezone = (this.timezone_select) ? this.timezone_select.val() : false, | |
750 | + o = this._defaults; | |
751 | + | |
752 | + if (typeof(hour) == 'object') { hour = false; } | |
753 | + if (typeof(minute) == 'object') { minute = false; } | |
754 | + if (typeof(second) == 'object') { second = false; } | |
755 | + if (typeof(millisec) == 'object') { millisec = false; } | |
756 | + if (typeof(timezone) == 'object') { timezone = false; } | |
757 | + | |
758 | + if (hour !== false) { hour = parseInt(hour,10); } | |
759 | + if (minute !== false) { minute = parseInt(minute,10); } | |
760 | + if (second !== false) { second = parseInt(second,10); } | |
761 | + if (millisec !== false) { millisec = parseInt(millisec,10); } | |
762 | + | |
763 | + var ampm = o[hour < 12 ? 'amNames' : 'pmNames'][0]; | |
764 | + | |
765 | + // If the update was done in the input field, the input field should not be updated. | |
766 | + // If the update was done using the sliders, update the input field. | |
767 | + var hasChanged = (hour != this.hour || minute != this.minute || | |
768 | + second != this.second || millisec != this.millisec || | |
769 | + (this.ampm.length > 0 && | |
770 | + (hour < 12) != ($.inArray(this.ampm.toUpperCase(), this.amNames) !== -1)) || | |
771 | + timezone != this.timezone); | |
772 | + | |
773 | + if (hasChanged) { | |
774 | + | |
775 | + if (hour !== false) { this.hour = hour; } | |
776 | + if (minute !== false) { this.minute = minute; } | |
777 | + if (second !== false) { this.second = second; } | |
778 | + if (millisec !== false) { this.millisec = millisec; } | |
779 | + if (timezone !== false) { this.timezone = timezone; } | |
780 | + | |
781 | + if (!this.inst) { this.inst = $.datepicker._getInst(this.$input[0]); } | |
782 | + | |
783 | + this._limitMinMaxDateTime(this.inst, true); | |
784 | + } | |
785 | + if (o.ampm) { this.ampm = ampm; } | |
786 | + | |
787 | + //this._formatTime(); | |
788 | + this.formattedTime = $.datepicker.formatTime(this._defaults.timeFormat, this, this._defaults); | |
789 | + if (this.$timeObj) { this.$timeObj.text(this.formattedTime + o.timeSuffix); } | |
790 | + this.timeDefined = true; | |
791 | + if (hasChanged) { this._updateDateTime(); } | |
792 | + }, | |
793 | + | |
794 | + //######################################################################## | |
795 | + // call custom onSelect. | |
796 | + // bind to sliders slidestop, and grid click. | |
797 | + //######################################################################## | |
798 | + _onSelectHandler: function() { | |
799 | + var onSelect = this._defaults.onSelect; | |
800 | + var inputEl = this.$input ? this.$input[0] : null; | |
801 | + if (onSelect && inputEl) { | |
802 | + onSelect.apply(inputEl, [this.formattedDateTime, this]); | |
803 | + } | |
804 | + }, | |
805 | + | |
806 | + //######################################################################## | |
807 | + // left for any backwards compatibility | |
808 | + //######################################################################## | |
809 | + _formatTime: function(time, format) { | |
810 | + time = time || { hour: this.hour, minute: this.minute, second: this.second, millisec: this.millisec, ampm: this.ampm, timezone: this.timezone }; | |
811 | + var tmptime = (format || this._defaults.timeFormat).toString(); | |
812 | + | |
813 | + tmptime = $.datepicker.formatTime(tmptime, time, this._defaults); | |
814 | + | |
815 | + if (arguments.length) { return tmptime; } | |
816 | + else { this.formattedTime = tmptime; } | |
817 | + }, | |
818 | + | |
819 | + //######################################################################## | |
820 | + // update our input with the new date time.. | |
821 | + //######################################################################## | |
822 | + _updateDateTime: function(dp_inst) { | |
823 | + dp_inst = this.inst || dp_inst; | |
824 | + var dt = $.datepicker._daylightSavingAdjust(new Date(dp_inst.selectedYear, dp_inst.selectedMonth, dp_inst.selectedDay)), | |
825 | + dateFmt = $.datepicker._get(dp_inst, 'dateFormat'), | |
826 | + formatCfg = $.datepicker._getFormatConfig(dp_inst), | |
827 | + timeAvailable = dt !== null && this.timeDefined; | |
828 | + this.formattedDate = $.datepicker.formatDate(dateFmt, (dt === null ? new Date() : dt), formatCfg); | |
829 | + var formattedDateTime = this.formattedDate; | |
830 | + // remove following lines to force every changes in date picker to change the input value | |
831 | + // Bug descriptions: when an input field has a default value, and click on the field to pop up the date picker. | |
832 | + // If the user manually empty the value in the input field, the date picker will never change selected value. | |
833 | + //if (dp_inst.lastVal !== undefined && (dp_inst.lastVal.length > 0 && this.$input.val().length === 0)) { | |
834 | + // return; | |
835 | + //} | |
836 | + | |
837 | + if (this._defaults.timeOnly === true) { | |
838 | + formattedDateTime = this.formattedTime; | |
839 | + } else if (this._defaults.timeOnly !== true && (this._defaults.alwaysSetTime || timeAvailable)) { | |
840 | + formattedDateTime += this._defaults.separator + this.formattedTime + this._defaults.timeSuffix; | |
841 | + } | |
842 | + | |
843 | + this.formattedDateTime = formattedDateTime; | |
844 | + | |
845 | + if(!this._defaults.showTimepicker) { | |
846 | + this.$input.val(this.formattedDate); | |
847 | + } else if (this.$altInput && this._defaults.altFieldTimeOnly === true) { | |
848 | + this.$altInput.val(this.formattedTime); | |
849 | + this.$input.val(this.formattedDate); | |
850 | + } else if(this.$altInput) { | |
851 | + this.$altInput.val(formattedDateTime); | |
852 | + this.$input.val(formattedDateTime); | |
853 | + } else { | |
854 | + this.$input.val(formattedDateTime); | |
855 | + } | |
856 | + | |
857 | + this.$input.trigger("change"); | |
858 | + } | |
859 | + | |
860 | +}); | |
861 | + | |
862 | +$.fn.extend({ | |
863 | + //######################################################################## | |
864 | + // shorthand just to use timepicker.. | |
865 | + //######################################################################## | |
866 | + timepicker: function(o) { | |
867 | + o = o || {}; | |
868 | + var tmp_args = arguments; | |
869 | + | |
870 | + if (typeof o == 'object') { tmp_args[0] = $.extend(o, { timeOnly: true }); } | |
871 | + | |
872 | + return $(this).each(function() { | |
873 | + $.fn.datetimepicker.apply($(this), tmp_args); | |
874 | + }); | |
875 | + }, | |
876 | + | |
877 | + //######################################################################## | |
878 | + // extend timepicker to datepicker | |
879 | + //######################################################################## | |
880 | + datetimepicker: function(o) { | |
881 | + o = o || {}; | |
882 | + var tmp_args = arguments; | |
883 | + | |
884 | + if (typeof(o) == 'string'){ | |
885 | + if(o == 'getDate') { | |
886 | + return $.fn.datepicker.apply($(this[0]), tmp_args); | |
887 | + } | |
888 | + else { | |
889 | + return this.each(function() { | |
890 | + var $t = $(this); | |
891 | + $t.datepicker.apply($t, tmp_args); | |
892 | + }); | |
893 | + } | |
894 | + } | |
895 | + else { | |
896 | + return this.each(function() { | |
897 | + var $t = $(this); | |
898 | + $t.datepicker($.timepicker._newInst($t, o)._defaults); | |
899 | + }); | |
900 | + } | |
901 | + } | |
902 | +}); | |
903 | + | |
904 | +$.datepicker.parseDateTime = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) { | |
905 | + var parseRes = parseDateTimeInternal(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings); | |
906 | + if (parseRes.timeObj) | |
907 | + { | |
908 | + var t = parseRes.timeObj; | |
909 | + parseRes.date.setHours(t.hour, t.minute, t.second, t.millisec); | |
910 | + } | |
911 | + | |
912 | + return parseRes.date; | |
913 | +}; | |
914 | + | |
915 | +$.datepicker.parseTime = function(timeFormat, timeString, options) { | |
916 | + | |
917 | + //######################################################################## | |
918 | + // pattern for standard and localized AM/PM markers | |
919 | + //######################################################################## | |
920 | + var getPatternAmpm = function(amNames, pmNames) { | |
921 | + var markers = []; | |
922 | + if (amNames) { | |
923 | + $.merge(markers, amNames); | |
924 | + } | |
925 | + if (pmNames) { | |
926 | + $.merge(markers, pmNames); | |
927 | + } | |
928 | + markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); }); | |
929 | + return '(' + markers.join('|') + ')?'; | |
930 | + }; | |
931 | + | |
932 | + //######################################################################## | |
933 | + // figure out position of time elements.. cause js cant do named captures | |
934 | + //######################################################################## | |
935 | + var getFormatPositions = function( timeFormat ) { | |
936 | + var finds = timeFormat.toLowerCase().match(/(h{1,2}|m{1,2}|s{1,2}|l{1}|t{1,2}|z)/g), | |
937 | + orders = { h: -1, m: -1, s: -1, l: -1, t: -1, z: -1 }; | |
938 | + | |
939 | + if (finds) { | |
940 | + for (var i = 0; i < finds.length; i++) { | |
941 | + if (orders[finds[i].toString().charAt(0)] == -1) { | |
942 | + orders[finds[i].toString().charAt(0)] = i + 1; | |
943 | + } | |
944 | + } | |
945 | + } | |
946 | + return orders; | |
947 | + }; | |
948 | + | |
949 | + var o = extendRemove(extendRemove({}, $.timepicker._defaults), options || {}); | |
950 | + | |
951 | + var regstr = '^' + timeFormat.toString() | |
952 | + .replace(/h{1,2}/ig, '(\\d?\\d)') | |
953 | + .replace(/m{1,2}/ig, '(\\d?\\d)') | |
954 | + .replace(/s{1,2}/ig, '(\\d?\\d)') | |
955 | + .replace(/l{1}/ig, '(\\d?\\d?\\d)') | |
956 | + .replace(/t{1,2}/ig, getPatternAmpm(o.amNames, o.pmNames)) | |
957 | + .replace(/z{1}/ig, '(z|[-+]\\d\\d:?\\d\\d)?') | |
958 | + .replace(/\s/g, '\\s?') + o.timeSuffix + '$', | |
959 | + order = getFormatPositions(timeFormat), | |
960 | + ampm = '', | |
961 | + treg; | |
962 | + | |
963 | + treg = timeString.match(new RegExp(regstr, 'i')); | |
964 | + | |
965 | + var resTime = {hour: 0, minute: 0, second: 0, millisec: 0}; | |
966 | + | |
967 | + if (treg) { | |
968 | + if (order.t !== -1) { | |
969 | + if (treg[order.t] === undefined || treg[order.t].length === 0) { | |
970 | + ampm = ''; | |
971 | + resTime.ampm = ''; | |
972 | + } else { | |
973 | + ampm = $.inArray(treg[order.t], o.amNames) !== -1 ? 'AM' : 'PM'; | |
974 | + resTime.ampm = o[ampm == 'AM' ? 'amNames' : 'pmNames'][0]; | |
975 | + } | |
976 | + } | |
977 | + | |
978 | + if (order.h !== -1) { | |
979 | + if (ampm == 'AM' && treg[order.h] == '12') { | |
980 | + resTime.hour = 0; // 12am = 0 hour | |
981 | + } else { | |
982 | + if (ampm == 'PM' && treg[order.h] != '12') { | |
983 | + resTime.hour = parseInt(treg[order.h],10) + 12; // 12pm = 12 hour, any other pm = hour + 12 | |
984 | + } | |
985 | + else { resTime.hour = Number(treg[order.h]); } | |
986 | + } | |
987 | + } | |
988 | + | |
989 | + if (order.m !== -1) { resTime.minute = Number(treg[order.m]); } | |
990 | + if (order.s !== -1) { resTime.second = Number(treg[order.s]); } | |
991 | + if (order.l !== -1) { resTime.millisec = Number(treg[order.l]); } | |
992 | + if (order.z !== -1 && treg[order.z] !== undefined) { | |
993 | + var tz = treg[order.z].toUpperCase(); | |
994 | + switch (tz.length) { | |
995 | + case 1: // Z | |
996 | + tz = o.timezoneIso8601 ? 'Z' : '+0000'; | |
997 | + break; | |
998 | + case 5: // +hhmm | |
999 | + if (o.timezoneIso8601) { | |
1000 | + tz = tz.substring(1) == '0000' ? | |
1001 | + 'Z' : | |
1002 | + tz.substring(0, 3) + ':' + tz.substring(3); | |
1003 | + } | |
1004 | + break; | |
1005 | + case 6: // +hh:mm | |
1006 | + if (!o.timezoneIso8601) { | |
1007 | + tz = tz == 'Z' || tz.substring(1) == '00:00' ? | |
1008 | + '+0000' : | |
1009 | + tz.replace(/:/, ''); | |
1010 | + } else { | |
1011 | + if (tz.substring(1) == '00:00') { | |
1012 | + tz = 'Z'; | |
1013 | + } | |
1014 | + } | |
1015 | + break; | |
1016 | + } | |
1017 | + resTime.timezone = tz; | |
1018 | + } | |
1019 | + | |
1020 | + | |
1021 | + return resTime; | |
1022 | + } | |
1023 | + | |
1024 | + return false; | |
1025 | +}; | |
1026 | + | |
1027 | +//######################################################################## | |
1028 | +// format the time all pretty... | |
1029 | +// format = string format of the time | |
1030 | +// time = a {}, not a Date() for timezones | |
1031 | +// options = essentially the regional[].. amNames, pmNames, ampm | |
1032 | +//######################################################################## | |
1033 | +$.datepicker.formatTime = function(format, time, options) { | |
1034 | + options = options || {}; | |
1035 | + options = $.extend($.timepicker._defaults, options); | |
1036 | + time = $.extend({hour:0, minute:0, second:0, millisec:0, timezone:'+0000'}, time); | |
1037 | + | |
1038 | + var tmptime = format; | |
1039 | + var ampmName = options.amNames[0]; | |
1040 | + | |
1041 | + var hour = parseInt(time.hour, 10); | |
1042 | + if (options.ampm) { | |
1043 | + if (hour > 11){ | |
1044 | + ampmName = options.pmNames[0]; | |
1045 | + if(hour > 12) { | |
1046 | + hour = hour % 12; | |
1047 | + } | |
1048 | + } | |
1049 | + if (hour === 0) { | |
1050 | + hour = 12; | |
1051 | + } | |
1052 | + } | |
1053 | + tmptime = tmptime.replace(/(?:hh?|mm?|ss?|[tT]{1,2}|[lz])/g, function(match) { | |
1054 | + switch (match.toLowerCase()) { | |
1055 | + case 'hh': return ('0' + hour).slice(-2); | |
1056 | + case 'h': return hour; | |
1057 | + case 'mm': return ('0' + time.minute).slice(-2); | |
1058 | + case 'm': return time.minute; | |
1059 | + case 'ss': return ('0' + time.second).slice(-2); | |
1060 | + case 's': return time.second; | |
1061 | + case 'l': return ('00' + time.millisec).slice(-3); | |
1062 | + case 'z': return time.timezone; | |
1063 | + case 't': case 'tt': | |
1064 | + if (options.ampm) { | |
1065 | + if (match.length == 1) { | |
1066 | + ampmName = ampmName.charAt(0); | |
1067 | + } | |
1068 | + return match.charAt(0) == 'T' ? ampmName.toUpperCase() : ampmName.toLowerCase(); | |
1069 | + } | |
1070 | + return ''; | |
1071 | + } | |
1072 | + }); | |
1073 | + | |
1074 | + tmptime = $.trim(tmptime); | |
1075 | + return tmptime; | |
1076 | +}; | |
1077 | + | |
1078 | +//######################################################################## | |
1079 | +// the bad hack :/ override datepicker so it doesnt close on select | |
1080 | +// inspired: http://stackoverflow.com/questions/1252512/jquery-datepicker-prevent-closing-picker-when-clicking-a-date/1762378#1762378 | |
1081 | +//######################################################################## | |
1082 | +$.datepicker._base_selectDate = $.datepicker._selectDate; | |
1083 | +$.datepicker._selectDate = function (id, dateStr) { | |
1084 | + var inst = this._getInst($(id)[0]), | |
1085 | + tp_inst = this._get(inst, 'timepicker'); | |
1086 | + | |
1087 | + if (tp_inst) { | |
1088 | + tp_inst._limitMinMaxDateTime(inst, true); | |
1089 | + inst.inline = inst.stay_open = true; | |
1090 | + //This way the onSelect handler called from calendarpicker get the full dateTime | |
1091 | + this._base_selectDate(id, dateStr); | |
1092 | + inst.inline = inst.stay_open = false; | |
1093 | + this._notifyChange(inst); | |
1094 | + this._updateDatepicker(inst); | |
1095 | + } | |
1096 | + else { this._base_selectDate(id, dateStr); } | |
1097 | +}; | |
1098 | + | |
1099 | +//############################################################################################# | |
1100 | +// second bad hack :/ override datepicker so it triggers an event when changing the input field | |
1101 | +// and does not redraw the datepicker on every selectDate event | |
1102 | +//############################################################################################# | |
1103 | +$.datepicker._base_updateDatepicker = $.datepicker._updateDatepicker; | |
1104 | +$.datepicker._updateDatepicker = function(inst) { | |
1105 | + | |
1106 | + // don't popup the datepicker if there is another instance already opened | |
1107 | + var input = inst.input[0]; | |
1108 | + if($.datepicker._curInst && | |
1109 | + $.datepicker._curInst != inst && | |
1110 | + $.datepicker._datepickerShowing && | |
1111 | + $.datepicker._lastInput != input) { | |
1112 | + return; | |
1113 | + } | |
1114 | + | |
1115 | + if (typeof(inst.stay_open) !== 'boolean' || inst.stay_open === false) { | |
1116 | + | |
1117 | + this._base_updateDatepicker(inst); | |
1118 | + | |
1119 | + // Reload the time control when changing something in the input text field. | |
1120 | + var tp_inst = this._get(inst, 'timepicker'); | |
1121 | + if(tp_inst) { | |
1122 | + tp_inst._addTimePicker(inst); | |
1123 | + | |
1124 | + if (tp_inst._defaults.useLocalTimezone) { //checks daylight saving with the new date. | |
1125 | + var date = new Date(inst.selectedYear, inst.selectedMonth, inst.selectedDay, 12); | |
1126 | + selectLocalTimeZone(tp_inst, date); | |
1127 | + tp_inst._onTimeChange(); | |
1128 | + } | |
1129 | + } | |
1130 | + } | |
1131 | +}; | |
1132 | + | |
1133 | +//####################################################################################### | |
1134 | +// third bad hack :/ override datepicker so it allows spaces and colon in the input field | |
1135 | +//####################################################################################### | |
1136 | +$.datepicker._base_doKeyPress = $.datepicker._doKeyPress; | |
1137 | +$.datepicker._doKeyPress = function(event) { | |
1138 | + var inst = $.datepicker._getInst(event.target), | |
1139 | + tp_inst = $.datepicker._get(inst, 'timepicker'); | |
1140 | + | |
1141 | + if (tp_inst) { | |
1142 | + if ($.datepicker._get(inst, 'constrainInput')) { | |
1143 | + var ampm = tp_inst._defaults.ampm, | |
1144 | + dateChars = $.datepicker._possibleChars($.datepicker._get(inst, 'dateFormat')), | |
1145 | + datetimeChars = tp_inst._defaults.timeFormat.toString() | |
1146 | + .replace(/[hms]/g, '') | |
1147 | + .replace(/TT/g, ampm ? 'APM' : '') | |
1148 | + .replace(/Tt/g, ampm ? 'AaPpMm' : '') | |
1149 | + .replace(/tT/g, ampm ? 'AaPpMm' : '') | |
1150 | + .replace(/T/g, ampm ? 'AP' : '') | |
1151 | + .replace(/tt/g, ampm ? 'apm' : '') | |
1152 | + .replace(/t/g, ampm ? 'ap' : '') + | |
1153 | + " " + | |
1154 | + tp_inst._defaults.separator + | |
1155 | + tp_inst._defaults.timeSuffix + | |
1156 | + (tp_inst._defaults.showTimezone ? tp_inst._defaults.timezoneList.join('') : '') + | |
1157 | + (tp_inst._defaults.amNames.join('')) + | |
1158 | + (tp_inst._defaults.pmNames.join('')) + | |
1159 | + dateChars, | |
1160 | + chr = String.fromCharCode(event.charCode === undefined ? event.keyCode : event.charCode); | |
1161 | + return event.ctrlKey || (chr < ' ' || !dateChars || datetimeChars.indexOf(chr) > -1); | |
1162 | + } | |
1163 | + } | |
1164 | + | |
1165 | + return $.datepicker._base_doKeyPress(event); | |
1166 | +}; | |
1167 | + | |
1168 | +//####################################################################################### | |
1169 | +// Override key up event to sync manual input changes. | |
1170 | +//####################################################################################### | |
1171 | +$.datepicker._base_doKeyUp = $.datepicker._doKeyUp; | |
1172 | +$.datepicker._doKeyUp = function (event) { | |
1173 | + var inst = $.datepicker._getInst(event.target), | |
1174 | + tp_inst = $.datepicker._get(inst, 'timepicker'); | |
1175 | + | |
1176 | + if (tp_inst) { | |
1177 | + if (tp_inst._defaults.timeOnly && (inst.input.val() != inst.lastVal)) { | |
1178 | + try { | |
1179 | + $.datepicker._updateDatepicker(inst); | |
1180 | + } | |
1181 | + catch (err) { | |
1182 | + $.datepicker.log(err); | |
1183 | + } | |
1184 | + } | |
1185 | + } | |
1186 | + | |
1187 | + return $.datepicker._base_doKeyUp(event); | |
1188 | +}; | |
1189 | + | |
1190 | +//####################################################################################### | |
1191 | +// override "Today" button to also grab the time. | |
1192 | +//####################################################################################### | |
1193 | +$.datepicker._base_gotoToday = $.datepicker._gotoToday; | |
1194 | +$.datepicker._gotoToday = function(id) { | |
1195 | + var inst = this._getInst($(id)[0]), | |
1196 | + $dp = inst.dpDiv; | |
1197 | + this._base_gotoToday(id); | |
1198 | + var tp_inst = this._get(inst, 'timepicker'); | |
1199 | + selectLocalTimeZone(tp_inst); | |
1200 | + var now = new Date(); | |
1201 | + this._setTime(inst, now); | |
1202 | + $( '.ui-datepicker-today', $dp).click(); | |
1203 | +}; | |
1204 | + | |
1205 | +//####################################################################################### | |
1206 | +// Disable & enable the Time in the datetimepicker | |
1207 | +//####################################################################################### | |
1208 | +$.datepicker._disableTimepickerDatepicker = function(target) { | |
1209 | + var inst = this._getInst(target); | |
1210 | + if (!inst) { return; } | |
1211 | + | |
1212 | + var tp_inst = this._get(inst, 'timepicker'); | |
1213 | + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] | |
1214 | + if (tp_inst) { | |
1215 | + tp_inst._defaults.showTimepicker = false; | |
1216 | + tp_inst._updateDateTime(inst); | |
1217 | + } | |
1218 | +}; | |
1219 | + | |
1220 | +$.datepicker._enableTimepickerDatepicker = function(target) { | |
1221 | + var inst = this._getInst(target); | |
1222 | + if (!inst) { return; } | |
1223 | + | |
1224 | + var tp_inst = this._get(inst, 'timepicker'); | |
1225 | + $(target).datepicker('getDate'); // Init selected[Year|Month|Day] | |
1226 | + if (tp_inst) { | |
1227 | + tp_inst._defaults.showTimepicker = true; | |
1228 | + tp_inst._addTimePicker(inst); // Could be disabled on page load | |
1229 | + tp_inst._updateDateTime(inst); | |
1230 | + } | |
1231 | +}; | |
1232 | + | |
1233 | +//####################################################################################### | |
1234 | +// Create our own set time function | |
1235 | +//####################################################################################### | |
1236 | +$.datepicker._setTime = function(inst, date) { | |
1237 | + var tp_inst = this._get(inst, 'timepicker'); | |
1238 | + if (tp_inst) { | |
1239 | + var defaults = tp_inst._defaults, | |
1240 | + // calling _setTime with no date sets time to defaults | |
1241 | + hour = date ? date.getHours() : defaults.hour, | |
1242 | + minute = date ? date.getMinutes() : defaults.minute, | |
1243 | + second = date ? date.getSeconds() : defaults.second, | |
1244 | + millisec = date ? date.getMilliseconds() : defaults.millisec; | |
1245 | + //check if within min/max times.. | |
1246 | + // correct check if within min/max times. | |
1247 | + // Rewritten by Scott A. Woodward | |
1248 | + var hourEq = hour === defaults.hourMin, | |
1249 | + minuteEq = minute === defaults.minuteMin, | |
1250 | + secondEq = second === defaults.secondMin; | |
1251 | + var reset = false; | |
1252 | + if(hour < defaults.hourMin || hour > defaults.hourMax) | |
1253 | + reset = true; | |
1254 | + else if( (minute < defaults.minuteMin || minute > defaults.minuteMax) && hourEq) | |
1255 | + reset = true; | |
1256 | + else if( (second < defaults.secondMin || second > defaults.secondMax ) && hourEq && minuteEq) | |
1257 | + reset = true; | |
1258 | + else if( (millisec < defaults.millisecMin || millisec > defaults.millisecMax) && hourEq && minuteEq && secondEq) | |
1259 | + reset = true; | |
1260 | + if(reset) { | |
1261 | + hour = defaults.hourMin; | |
1262 | + minute = defaults.minuteMin; | |
1263 | + second = defaults.secondMin; | |
1264 | + millisec = defaults.millisecMin; | |
1265 | + } | |
1266 | + tp_inst.hour = hour; | |
1267 | + tp_inst.minute = minute; | |
1268 | + tp_inst.second = second; | |
1269 | + tp_inst.millisec = millisec; | |
1270 | + if (tp_inst.hour_slider) tp_inst.hour_slider.slider('value', hour); | |
1271 | + if (tp_inst.minute_slider) tp_inst.minute_slider.slider('value', minute); | |
1272 | + if (tp_inst.second_slider) tp_inst.second_slider.slider('value', second); | |
1273 | + if (tp_inst.millisec_slider) tp_inst.millisec_slider.slider('value', millisec); | |
1274 | + | |
1275 | + tp_inst._onTimeChange(); | |
1276 | + tp_inst._updateDateTime(inst); | |
1277 | + } | |
1278 | +}; | |
1279 | + | |
1280 | +//####################################################################################### | |
1281 | +// Create new public method to set only time, callable as $().datepicker('setTime', date) | |
1282 | +//####################################################################################### | |
1283 | +$.datepicker._setTimeDatepicker = function(target, date, withDate) { | |
1284 | + var inst = this._getInst(target); | |
1285 | + if (!inst) { return; } | |
1286 | + | |
1287 | + var tp_inst = this._get(inst, 'timepicker'); | |
1288 | + | |
1289 | + if (tp_inst) { | |
1290 | + this._setDateFromField(inst); | |
1291 | + var tp_date; | |
1292 | + if (date) { | |
1293 | + if (typeof date == "string") { | |
1294 | + tp_inst._parseTime(date, withDate); | |
1295 | + tp_date = new Date(); | |
1296 | + tp_date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); | |
1297 | + } | |
1298 | + else { tp_date = new Date(date.getTime()); } | |
1299 | + if (tp_date.toString() == 'Invalid Date') { tp_date = undefined; } | |
1300 | + this._setTime(inst, tp_date); | |
1301 | + } | |
1302 | + } | |
1303 | + | |
1304 | +}; | |
1305 | + | |
1306 | +//####################################################################################### | |
1307 | +// override setDate() to allow setting time too within Date object | |
1308 | +//####################################################################################### | |
1309 | +$.datepicker._base_setDateDatepicker = $.datepicker._setDateDatepicker; | |
1310 | +$.datepicker._setDateDatepicker = function(target, date) { | |
1311 | + var inst = this._getInst(target); | |
1312 | + if (!inst) { return; } | |
1313 | + | |
1314 | + var tp_date = (date instanceof Date) ? new Date(date.getTime()) : date; | |
1315 | + | |
1316 | + this._updateDatepicker(inst); | |
1317 | + this._base_setDateDatepicker.apply(this, arguments); | |
1318 | + this._setTimeDatepicker(target, tp_date, true); | |
1319 | +}; | |
1320 | + | |
1321 | +//####################################################################################### | |
1322 | +// override getDate() to allow getting time too within Date object | |
1323 | +//####################################################################################### | |
1324 | +$.datepicker._base_getDateDatepicker = $.datepicker._getDateDatepicker; | |
1325 | +$.datepicker._getDateDatepicker = function(target, noDefault) { | |
1326 | + var inst = this._getInst(target); | |
1327 | + if (!inst) { return; } | |
1328 | + | |
1329 | + var tp_inst = this._get(inst, 'timepicker'); | |
1330 | + | |
1331 | + if (tp_inst) { | |
1332 | + this._setDateFromField(inst, noDefault); | |
1333 | + var date = this._getDate(inst); | |
1334 | + if (date && tp_inst._parseTime($(target).val(), tp_inst.timeOnly)) { date.setHours(tp_inst.hour, tp_inst.minute, tp_inst.second, tp_inst.millisec); } | |
1335 | + return date; | |
1336 | + } | |
1337 | + return this._base_getDateDatepicker(target, noDefault); | |
1338 | +}; | |
1339 | + | |
1340 | +//####################################################################################### | |
1341 | +// override parseDate() because UI 1.8.14 throws an error about "Extra characters" | |
1342 | +// An option in datapicker to ignore extra format characters would be nicer. | |
1343 | +//####################################################################################### | |
1344 | +$.datepicker._base_parseDate = $.datepicker.parseDate; | |
1345 | +$.datepicker.parseDate = function(format, value, settings) { | |
1346 | + var splitRes = splitDateTime(format, value, settings); | |
1347 | + return $.datepicker._base_parseDate(format, splitRes[0], settings); | |
1348 | +}; | |
1349 | + | |
1350 | +//####################################################################################### | |
1351 | +// override formatDate to set date with time to the input | |
1352 | +//####################################################################################### | |
1353 | +$.datepicker._base_formatDate = $.datepicker._formatDate; | |
1354 | +$.datepicker._formatDate = function(inst, day, month, year){ | |
1355 | + var tp_inst = this._get(inst, 'timepicker'); | |
1356 | + if(tp_inst) { | |
1357 | + tp_inst._updateDateTime(inst); | |
1358 | + return tp_inst.$input.val(); | |
1359 | + } | |
1360 | + return this._base_formatDate(inst); | |
1361 | +}; | |
1362 | + | |
1363 | +//####################################################################################### | |
1364 | +// override options setter to add time to maxDate(Time) and minDate(Time). MaxDate | |
1365 | +//####################################################################################### | |
1366 | +$.datepicker._base_optionDatepicker = $.datepicker._optionDatepicker; | |
1367 | +$.datepicker._optionDatepicker = function(target, name, value) { | |
1368 | + var inst = this._getInst(target); | |
1369 | + if (!inst) { return null; } | |
1370 | + | |
1371 | + var tp_inst = this._get(inst, 'timepicker'); | |
1372 | + if (tp_inst) { | |
1373 | + var min = null, max = null, onselect = null; | |
1374 | + if (typeof name == 'string') { // if min/max was set with the string | |
1375 | + if (name === 'minDate' || name === 'minDateTime' ) { | |
1376 | + min = value; | |
1377 | + } | |
1378 | + else { | |
1379 | + if (name === 'maxDate' || name === 'maxDateTime') { | |
1380 | + max = value; | |
1381 | + } | |
1382 | + else { | |
1383 | + if (name === 'onSelect') { | |
1384 | + onselect = value; | |
1385 | + } | |
1386 | + } | |
1387 | + } | |
1388 | + } else { | |
1389 | + if (typeof name == 'object') { //if min/max was set with the JSON | |
1390 | + if (name.minDate) { | |
1391 | + min = name.minDate; | |
1392 | + } else { | |
1393 | + if (name.minDateTime) { | |
1394 | + min = name.minDateTime; | |
1395 | + } else { | |
1396 | + if (name.maxDate) { | |
1397 | + max = name.maxDate; | |
1398 | + } else { | |
1399 | + if (name.maxDateTime) { | |
1400 | + max = name.maxDateTime; | |
1401 | + } | |
1402 | + } | |
1403 | + } | |
1404 | + } | |
1405 | + } | |
1406 | + } | |
1407 | + if(min) { //if min was set | |
1408 | + if (min === 0) { | |
1409 | + min = new Date(); | |
1410 | + } else { | |
1411 | + min = new Date(min); | |
1412 | + } | |
1413 | + | |
1414 | + tp_inst._defaults.minDate = min; | |
1415 | + tp_inst._defaults.minDateTime = min; | |
1416 | + } else if (max) { //if max was set | |
1417 | + if(max===0) { | |
1418 | + max=new Date(); | |
1419 | + } else { | |
1420 | + max= new Date(max); | |
1421 | + } | |
1422 | + tp_inst._defaults.maxDate = max; | |
1423 | + tp_inst._defaults.maxDateTime = max; | |
1424 | + } else if (onselect) { | |
1425 | + tp_inst._defaults.onSelect = onselect; | |
1426 | + } | |
1427 | + } | |
1428 | + if (value === undefined) { | |
1429 | + return this._base_optionDatepicker(target, name); | |
1430 | + } | |
1431 | + return this._base_optionDatepicker(target, name, value); | |
1432 | +}; | |
1433 | + | |
1434 | +//####################################################################################### | |
1435 | +// jQuery extend now ignores nulls! | |
1436 | +//####################################################################################### | |
1437 | +function extendRemove(target, props) { | |
1438 | + $.extend(target, props); | |
1439 | + for (var name in props) { | |
1440 | + if (props[name] === null || props[name] === undefined) { | |
1441 | + target[name] = props[name]; | |
1442 | + } | |
1443 | + } | |
1444 | + return target; | |
1445 | +} | |
1446 | + | |
1447 | +//####################################################################################### | |
1448 | +// Splits datetime string into date ans time substrings. | |
1449 | +// Throws exception when date can't be parsed | |
1450 | +// If only date is present, time substring eill be '' | |
1451 | +//####################################################################################### | |
1452 | +var splitDateTime = function(dateFormat, dateTimeString, dateSettings) | |
1453 | +{ | |
1454 | + try { | |
1455 | + var date = $.datepicker._base_parseDate(dateFormat, dateTimeString, dateSettings); | |
1456 | + } catch (err) { | |
1457 | + if (err.indexOf(":") >= 0) { | |
1458 | + // Hack! The error message ends with a colon, a space, and | |
1459 | + // the "extra" characters. We rely on that instead of | |
1460 | + // attempting to perfectly reproduce the parsing algorithm. | |
1461 | + var dateStringLength = dateTimeString.length-(err.length-err.indexOf(':')-2); | |
1462 | + var timeString = dateTimeString.substring(dateStringLength); | |
1463 | + | |
1464 | + return [dateTimeString.substring(0, dateStringLength), dateTimeString.substring(dateStringLength)]; | |
1465 | + | |
1466 | + } else { | |
1467 | + throw err; | |
1468 | + } | |
1469 | + } | |
1470 | + return [dateTimeString, '']; | |
1471 | +}; | |
1472 | + | |
1473 | +//####################################################################################### | |
1474 | +// Internal function to parse datetime interval | |
1475 | +// Returns: {date: Date, timeObj: Object}, where | |
1476 | +// date - parsed date without time (type Date) | |
1477 | +// timeObj = {hour: , minute: , second: , millisec: } - parsed time. Optional | |
1478 | +//####################################################################################### | |
1479 | +var parseDateTimeInternal = function(dateFormat, timeFormat, dateTimeString, dateSettings, timeSettings) | |
1480 | +{ | |
1481 | + var date; | |
1482 | + var splitRes = splitDateTime(dateFormat, dateTimeString, dateSettings); | |
1483 | + date = $.datepicker._base_parseDate(dateFormat, splitRes[0], dateSettings); | |
1484 | + if (splitRes[1] !== '') | |
1485 | + { | |
1486 | + var timeString = splitRes[1]; | |
1487 | + var separator = timeSettings && timeSettings.separator ? timeSettings.separator : $.timepicker._defaults.separator; | |
1488 | + if ( timeString.indexOf(separator) !== 0) { | |
1489 | + throw 'Missing time separator'; | |
1490 | + } | |
1491 | + timeString = timeString.substring(separator.length); | |
1492 | + var parsedTime = $.datepicker.parseTime(timeFormat, timeString, timeSettings); | |
1493 | + if (parsedTime === null) { | |
1494 | + throw 'Wrong time format'; | |
1495 | + } | |
1496 | + return {date: date, timeObj: parsedTime}; | |
1497 | + } else { | |
1498 | + return {date: date}; | |
1499 | + } | |
1500 | +}; | |
1501 | + | |
1502 | +//####################################################################################### | |
1503 | +// Internal function to set timezone_select to the local timezone | |
1504 | +//####################################################################################### | |
1505 | +var selectLocalTimeZone = function(tp_inst, date) | |
1506 | +{ | |
1507 | + if (tp_inst && tp_inst.timezone_select) { | |
1508 | + tp_inst._defaults.useLocalTimezone = true; | |
1509 | + var now = typeof date !== 'undefined' ? date : new Date(); | |
1510 | + var tzoffset = timeZoneString(now); | |
1511 | + if (tp_inst._defaults.timezoneIso8601) { | |
1512 | + tzoffset = tzoffset.substring(0, 3) + ':' + tzoffset.substring(3); | |
1513 | + } | |
1514 | + tp_inst.timezone_select.val(tzoffset); | |
1515 | + } | |
1516 | +}; | |
1517 | + | |
1518 | +// Input: Date Object | |
1519 | +// Output: String with timezone offset, e.g. '+0100' | |
1520 | +var timeZoneString = function(date) | |
1521 | +{ | |
1522 | + var off = date.getTimezoneOffset() * -10100 / 60; | |
1523 | + var timezone = (off >= 0 ? '+' : '-') + Math.abs(off).toString().substr(1); | |
1524 | + return timezone; | |
1525 | +}; | |
1526 | + | |
1527 | +$.timepicker = new Timepicker(); // singleton instance | |
1528 | +$.timepicker.version = "1.0.1"; | |
1529 | + | |
1530 | +})(jQuery); | ... | ... |
public/stylesheets/application.css
... | ... | @@ -4340,6 +4340,14 @@ h1#agenda-title { |
4340 | 4340 | margin: 5px 0; |
4341 | 4341 | padding: 5px; |
4342 | 4342 | } |
4343 | + | |
4344 | +.ui-timepicker-div .ui-widget-header { margin-bottom: 8px; } | |
4345 | +.ui-timepicker-div dl { text-align: left; } | |
4346 | +.ui-timepicker-div dl dt { height: 25px; margin-bottom: -25px; } | |
4347 | +.ui-timepicker-div dl dd { margin: 0 10px 10px 65px; } | |
4348 | +.ui-timepicker-div td { font-size: 90%; } | |
4349 | +.ui-tpicker-grid-label { background: none; border: none; margin: 0; padding: 0; } | |
4350 | + | |
4343 | 4351 | /* Categories block stuff */ |
4344 | 4352 | |
4345 | 4353 | .categories-block ul { | ... | ... |