puffer 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. data/.rspec +1 -0
  2. data/Gemfile +2 -1
  3. data/Gemfile.lock +12 -0
  4. data/VERSION +1 -1
  5. data/app/cells/puffer/base/additional.html.erb +17 -0
  6. data/app/cells/puffer/base_cell.rb +25 -0
  7. data/app/controllers/admin/dashboard_controller.rb +12 -0
  8. data/app/views/admin/dashboard/index.html.erb +1 -0
  9. data/app/views/layouts/puffer.html.erb +41 -23
  10. data/app/views/puffer/_form.html.erb +3 -11
  11. data/app/views/puffer/edit.html.erb +6 -4
  12. data/app/views/puffer/index.html.erb +11 -38
  13. data/app/views/puffer/new.html.erb +5 -2
  14. data/app/views/puffer/show.html.erb +4 -4
  15. data/config/routes.rb +7 -0
  16. data/lib/generators/puffer/controller/controller_generator.rb +1 -1
  17. data/lib/generators/puffer/install/templates/puffer/javascripts/rails-src.js +399 -0
  18. data/lib/generators/puffer/install/templates/puffer/javascripts/rails.js +14 -175
  19. data/lib/generators/puffer/install/templates/puffer/javascripts/right-calendar-src.js +1461 -0
  20. data/lib/generators/puffer/install/templates/puffer/javascripts/right-calendar.js +36 -0
  21. data/lib/generators/puffer/install/templates/puffer/javascripts/right-calendar.js.gz +0 -0
  22. data/lib/generators/puffer/install/templates/puffer/javascripts/right-in-edit-src.js +369 -0
  23. data/lib/generators/puffer/install/templates/puffer/javascripts/right-in-edit.js +13 -0
  24. data/lib/generators/puffer/install/templates/puffer/javascripts/right-in-edit.js.gz +0 -0
  25. data/lib/generators/puffer/install/templates/puffer/javascripts/right-lightbox-src.js +905 -0
  26. data/lib/generators/puffer/install/templates/puffer/javascripts/right-lightbox.js +24 -0
  27. data/lib/generators/puffer/install/templates/puffer/javascripts/right-lightbox.js.gz +0 -0
  28. data/lib/generators/puffer/install/templates/puffer/javascripts/right-sortable-src.js +428 -0
  29. data/lib/generators/puffer/install/templates/puffer/javascripts/right-sortable.js +17 -0
  30. data/lib/generators/puffer/install/templates/puffer/javascripts/right-sortable.js.gz +0 -0
  31. data/lib/generators/puffer/install/templates/puffer/javascripts/right-src.js +5892 -0
  32. data/lib/generators/puffer/install/templates/puffer/javascripts/right-tabs-src.js +1145 -0
  33. data/lib/generators/puffer/install/templates/puffer/javascripts/right-tabs.js +29 -0
  34. data/lib/generators/puffer/install/templates/puffer/javascripts/right-tabs.js.gz +0 -0
  35. data/lib/generators/puffer/install/templates/puffer/javascripts/right.js +95 -0
  36. data/lib/generators/puffer/install/templates/puffer/javascripts/right.js.gz +0 -0
  37. data/lib/generators/puffer/install/templates/puffer/stylesheets/puffer.css +168 -0
  38. data/lib/generators/puffer/install/templates/puffer/stylesheets/reset.css +60 -0
  39. data/lib/puffer.rb +2 -1
  40. data/lib/puffer/base.rb +15 -14
  41. data/lib/puffer/controller/config.rb +52 -16
  42. data/lib/puffer/controller/dsl.rb +27 -29
  43. data/lib/puffer/controller/helpers.rb +54 -9
  44. data/lib/puffer/controller/mutate.rb +4 -22
  45. data/lib/puffer/engine.rb +9 -0
  46. data/lib/puffer/extensions/controller.rb +5 -3
  47. data/lib/puffer/extensions/form.rb +14 -0
  48. data/lib/puffer/extensions/mapper.rb +36 -0
  49. data/lib/puffer/fields.rb +21 -0
  50. data/lib/puffer/fields/field.rb +8 -3
  51. data/lib/puffer/inputs.rb +23 -0
  52. data/lib/puffer/inputs/association.rb +11 -0
  53. data/lib/puffer/inputs/base.rb +39 -0
  54. data/lib/puffer/inputs/boolean.rb +19 -0
  55. data/lib/puffer/inputs/collection_association.rb +11 -0
  56. data/lib/puffer/inputs/date_time.rb +16 -0
  57. data/lib/puffer/inputs/file.rb +11 -0
  58. data/lib/puffer/inputs/password.rb +11 -0
  59. data/lib/puffer/inputs/select.rb +11 -0
  60. data/lib/puffer/inputs/text.rb +11 -0
  61. data/lib/puffer/path_set.rb +17 -0
  62. data/lib/puffer/resource.rb +14 -14
  63. data/puffer.gemspec +75 -8
  64. data/spec/dummy/app/controllers/admin/categories_controller.rb +4 -0
  65. data/spec/dummy/app/controllers/admin/posts_controller.rb +4 -0
  66. data/spec/dummy/app/controllers/admin/profiles_controller.rb +5 -0
  67. data/spec/dummy/app/controllers/admin/users_controller.rb +4 -0
  68. data/spec/dummy/app/models/friendship.rb +4 -0
  69. data/spec/dummy/app/models/profile.rb +2 -0
  70. data/spec/dummy/app/models/user.rb +7 -0
  71. data/spec/dummy/app/views/admin/users/index.html.erb +1 -0
  72. data/spec/dummy/config/puffer.rb +0 -0
  73. data/spec/dummy/db/migrate/20110107082706_create_friendships.rb +15 -0
  74. data/spec/dummy/db/schema.rb +9 -1
  75. data/spec/dummy/public/puffer/javascripts/rails-src.js +399 -0
  76. data/spec/dummy/public/puffer/javascripts/rails.js +14 -0
  77. data/spec/dummy/public/puffer/javascripts/right-calendar-src.js +1461 -0
  78. data/spec/dummy/public/puffer/javascripts/right-calendar.js +36 -0
  79. data/spec/dummy/public/puffer/javascripts/right-calendar.js.gz +0 -0
  80. data/spec/dummy/public/puffer/javascripts/right-in-edit-src.js +369 -0
  81. data/spec/dummy/public/puffer/javascripts/right-in-edit.js +13 -0
  82. data/spec/dummy/public/puffer/javascripts/right-in-edit.js.gz +0 -0
  83. data/spec/dummy/public/puffer/javascripts/right-lightbox-src.js +905 -0
  84. data/spec/dummy/public/puffer/javascripts/right-lightbox.js +24 -0
  85. data/spec/dummy/public/puffer/javascripts/right-lightbox.js.gz +0 -0
  86. data/spec/dummy/public/puffer/javascripts/right-sortable-src.js +428 -0
  87. data/spec/dummy/public/puffer/javascripts/right-sortable.js +17 -0
  88. data/spec/dummy/public/puffer/javascripts/right-sortable.js.gz +0 -0
  89. data/spec/dummy/public/puffer/javascripts/right-src.js +5892 -0
  90. data/spec/dummy/public/puffer/javascripts/right-tabs-src.js +1145 -0
  91. data/spec/dummy/public/puffer/javascripts/right-tabs.js +29 -0
  92. data/spec/dummy/public/puffer/javascripts/right-tabs.js.gz +0 -0
  93. data/spec/dummy/public/puffer/javascripts/right.js +95 -0
  94. data/spec/dummy/public/puffer/javascripts/right.js.gz +0 -0
  95. data/spec/dummy/public/puffer/stylesheets/puffer.css +168 -0
  96. data/spec/dummy/public/puffer/stylesheets/reset.css +60 -0
  97. data/spec/lib/fields_spec.rb +2 -0
  98. data/spec/lib/params_spec.rb +55 -54
  99. metadata +114 -23
  100. data/lib/generators/puffer/install/templates/puffer/javascripts/application.js +0 -2
  101. data/lib/generators/puffer/install/templates/puffer/javascripts/controls.js +0 -965
  102. data/lib/generators/puffer/install/templates/puffer/javascripts/dragdrop.js +0 -974
  103. data/lib/generators/puffer/install/templates/puffer/javascripts/effects.js +0 -1123
  104. data/lib/generators/puffer/install/templates/puffer/javascripts/prototype.js +0 -6001
  105. data/lib/puffer/railtie.rb +0 -5
@@ -1,175 +1,14 @@
1
- (function() {
2
- // Technique from Juriy Zaytsev
3
- // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
4
- function isEventSupported(eventName) {
5
- var el = document.createElement('div');
6
- eventName = 'on' + eventName;
7
- var isSupported = (eventName in el);
8
- if (!isSupported) {
9
- el.setAttribute(eventName, 'return;');
10
- isSupported = typeof el[eventName] == 'function';
11
- }
12
- el = null;
13
- return isSupported;
14
- }
15
-
16
- function isForm(element) {
17
- return Object.isElement(element) && element.nodeName.toUpperCase() == 'FORM'
18
- }
19
-
20
- function isInput(element) {
21
- if (Object.isElement(element)) {
22
- var name = element.nodeName.toUpperCase()
23
- return name == 'INPUT' || name == 'SELECT' || name == 'TEXTAREA'
24
- }
25
- else return false
26
- }
27
-
28
- var submitBubbles = isEventSupported('submit'),
29
- changeBubbles = isEventSupported('change')
30
-
31
- if (!submitBubbles || !changeBubbles) {
32
- // augment the Event.Handler class to observe custom events when needed
33
- Event.Handler.prototype.initialize = Event.Handler.prototype.initialize.wrap(
34
- function(init, element, eventName, selector, callback) {
35
- init(element, eventName, selector, callback)
36
- // is the handler being attached to an element that doesn't support this event?
37
- if ( (!submitBubbles && this.eventName == 'submit' && !isForm(this.element)) ||
38
- (!changeBubbles && this.eventName == 'change' && !isInput(this.element)) ) {
39
- // "submit" => "emulated:submit"
40
- this.eventName = 'emulated:' + this.eventName
41
- }
42
- }
43
- )
44
- }
45
-
46
- if (!submitBubbles) {
47
- // discover forms on the page by observing focus events which always bubble
48
- document.on('focusin', 'form', function(focusEvent, form) {
49
- // special handler for the real "submit" event (one-time operation)
50
- if (!form.retrieve('emulated:submit')) {
51
- form.on('submit', function(submitEvent) {
52
- var emulated = form.fire('emulated:submit', submitEvent, true)
53
- // if custom event received preventDefault, cancel the real one too
54
- if (emulated.returnValue === false) submitEvent.preventDefault()
55
- })
56
- form.store('emulated:submit', true)
57
- }
58
- })
59
- }
60
-
61
- if (!changeBubbles) {
62
- // discover form inputs on the page
63
- document.on('focusin', 'input, select, texarea', function(focusEvent, input) {
64
- // special handler for real "change" events
65
- if (!input.retrieve('emulated:change')) {
66
- input.on('change', function(changeEvent) {
67
- input.fire('emulated:change', changeEvent, true)
68
- })
69
- input.store('emulated:change', true)
70
- }
71
- })
72
- }
73
-
74
- function handleRemote(element) {
75
- var method, url, params;
76
-
77
- var event = element.fire("ajax:before");
78
- if (event.stopped) return false;
79
-
80
- if (element.tagName.toLowerCase() === 'form') {
81
- method = element.readAttribute('method') || 'post';
82
- url = element.readAttribute('action');
83
- params = element.serialize();
84
- } else {
85
- method = element.readAttribute('data-method') || 'get';
86
- url = element.readAttribute('href');
87
- params = {};
88
- }
89
-
90
- new Ajax.Request(url, {
91
- method: method,
92
- parameters: params,
93
- evalScripts: true,
94
-
95
- onComplete: function(request) { element.fire("ajax:complete", request); },
96
- onSuccess: function(request) { element.fire("ajax:success", request); },
97
- onFailure: function(request) { element.fire("ajax:failure", request); }
98
- });
99
-
100
- element.fire("ajax:after");
101
- }
102
-
103
- function handleMethod(element) {
104
- var method = element.readAttribute('data-method'),
105
- url = element.readAttribute('href'),
106
- csrf_param = $$('meta[name=csrf-param]')[0],
107
- csrf_token = $$('meta[name=csrf-token]')[0];
108
-
109
- var form = new Element('form', { method: "POST", action: url, style: "display: none;" });
110
- element.parentNode.insert(form);
111
-
112
- if (method !== 'post') {
113
- var field = new Element('input', { type: 'hidden', name: '_method', value: method });
114
- form.insert(field);
115
- }
116
-
117
- if (csrf_param) {
118
- var param = csrf_param.readAttribute('content'),
119
- token = csrf_token.readAttribute('content'),
120
- field = new Element('input', { type: 'hidden', name: param, value: token });
121
- form.insert(field);
122
- }
123
-
124
- form.submit();
125
- }
126
-
127
-
128
- document.on("click", "*[data-confirm]", function(event, element) {
129
- var message = element.readAttribute('data-confirm');
130
- if (!confirm(message)) event.stop();
131
- });
132
-
133
- document.on("click", "a[data-remote]", function(event, element) {
134
- if (event.stopped) return;
135
- handleRemote(element);
136
- event.stop();
137
- });
138
-
139
- document.on("click", "a[data-method]", function(event, element) {
140
- if (event.stopped) return;
141
- handleMethod(element);
142
- event.stop();
143
- });
144
-
145
- document.on("submit", function(event) {
146
- var element = event.findElement(),
147
- message = element.readAttribute('data-confirm');
148
- if (message && !confirm(message)) {
149
- event.stop();
150
- return false;
151
- }
152
-
153
- var inputs = element.select("input[type=submit][data-disable-with]");
154
- inputs.each(function(input) {
155
- input.disabled = true;
156
- input.writeAttribute('data-original-value', input.value);
157
- input.value = input.readAttribute('data-disable-with');
158
- });
159
-
160
- var element = event.findElement("form[data-remote]");
161
- if (element) {
162
- handleRemote(element);
163
- event.stop();
164
- }
165
- });
166
-
167
- document.on("ajax:after", "form", function(event, element) {
168
- var inputs = element.select("input[type=submit][disabled=true][data-disable-with]");
169
- inputs.each(function(input) {
170
- input.value = input.readAttribute('data-original-value');
171
- input.removeAttribute('data-original-value');
172
- input.disabled = false;
173
- });
174
- });
175
- })();
1
+ /**
2
+ * The Ruby On Rails plugin for RightJS
3
+ * http://github.com/MadRabbit/right-rails
4
+ *
5
+ * Copyright (C) 2009-2010 Nikolay Nemshilov
6
+ */
7
+ (function(i,j,e){var f=e.$,l=e.$$,o=e.$E,m=e.Xhr,p=e.Object;e([e.String.prototype,e.Array.prototype,e.Function.prototype,e.Object,e.Options,e.Observer,e.Observer.prototype,i,j]).each(function(a){for(var b in a)try{if(/[A-Z]/.test(b)&&typeof a[b]==="function"){var c=e(b).underscored();if(a[c]===null||a[c]===undefined)a[c]=a[b]}}catch(d){}});e([e.Element,e.Event,e.Form,e.Input]).each(function(a){if(a){var b=a.prototype;for(var c in b)if(/[A-Z]/.test(c)&&typeof b[c]==="function")a.prototype[e(c).underscored()]=
8
+ b[c]}});e.$alias(e.String.prototype,{index_of:"indexOf",last_index_of:"lastIndexOf",to_f:"toFloat",to_i:"toInt",gsub:"replace",downcase:"toLowerCase",upcase:"toUpperCase",index:"indexOf",rindex:"lastIndexOf",strip:"trim"});e.$alias(e.Array.prototype,{collect:"map",detect:"filter",index_of:"indexOf",last_index_of:"lastIndexOf",index:"indexOf",rindex:"lastIndexOf"});(function(){var a=function(c,d){var g=d.get("data-confirm");if(g&&!confirm(g)){c.stop();return true}},b=function(c,d){return p.merge({onCreate:function(){c.fire("ajax:loading",
9
+ this)},onComplete:function(){c.fire("ajax:complete",this)},onSuccess:function(){c.fire("ajax:success",this)},onFailure:function(){c.fire("ajax:failure",this)}},d)};f(j).on({click:function(c){var d=c.target._.tagName;if(d==="A"||d==="BUTTON"){var g=c.target;d=g.get("data-method");var n=g.get("data-remote"),h=g.get("href");if(!a(c,g)){if(d||n)c.stop();if(n)m.load(h,b(g,{method:d||"get",spinner:g.get("data-spinner")}));else if(d){c=l("meta[name=csrf-param]")[0];g=l("meta[name=csrf-token]")[0];h=o("form",
10
+ {action:h,method:"post"});c&&g&&h.insert('<input type="hidden" name="'+c.get("content")+'" value="'+g.get("content")+'" />');h.insert('<input type="hidden" name="_method" value="'+d+'"/>').insertTo(j.body).submit()}}}},submit:function(c){var d=c.target;if(d.has("data-remote")&&!a(c,d)){c.stop();d.send(b(d,{spinner:d.get("data-spinner")||d.first(".spinner")}))}}})})();var k={Options:{format:"js",flashId:"flashes",flashHideFx:"slide",flashHideDelay:3200,highlightUpdates:true,removeFx:"fade",insertFx:"fade",
11
+ insertPosition:"bottom",linkToAjaxEdit:".ajax_edit",linkToAjaxDelete:".ajax_delete",rescanWithScopes:true},update_flash:function(a){var b=f(this.Options.flashId);b&&this.replace(b,a).hide_flash();return this},hide_flash:function(){if(this.Options.flashHideDelay>-1){var a=f(this.Options.flashId);a&&a.visible()&&a.hide.bind(a,this.Options.flashHideFx).delay(this.Options.flashHideDelay)}return this},highlight:function(a){f(a)&&this.Options.highlightUpdates&&f(a).highlight();return this},insert:function(a,
12
+ b,c){c=c||this.Options.insertPosition;var d;b=f(a).insert(b,c);switch(c){case "bottom":d=b.children().last();break;case "top":d=b.first();break;case "before":d=b.prev();break;case "after":d=b.next();break}d&&this.Options.insertFx?d.hide().show(this.Options.insertFx,{onFinish:this.highlight.bind(this,d)}):this.highlight(d);return this.rescan(a)},replace:function(a,b){f(a).replace(b);return this.highlight(a).rescan(a)},remove:function(a){f(a)&&f(a).remove(this.Options.removeFx)},remotize_form:function(a){if(a=
13
+ f(a))a.remotize().enable()._.action+="."+this.Options.format;return this},replace_form:function(a,b){var c=f(a);if(c){c.replace(b);this.remotize_form(a)}return this.rescan(a)},show_form_for:function(a,b){f(a).select("form").each("remove");f(a).insert(b);return this.remotize_form(f(a).first("form")).rescan(a)},process_click:function(a){var b;if(b=a.find("a"+this.Options.linkToAjaxEdit)){a.stop();m.load(b.get("href")+"."+this.Options.format)}else if((b=a.find("a"+this.Options.linkToAjaxDelete))&&b.has("onclick")){a.stop();
14
+ eval("({f:"+b.onclick.toString().replace(".submit",".send")+"})").f.call(b)}},rescan:function(a){$w("Draggable Droppable Tabs Slider Selectable").each(function(b){if(b in i)i[b].rescan(this.Options.rescanWithScopes?a:null)},this);return this}};f(j).on({ready:function(){k.hide_flash()},click:function(a){k.process_click(a)}});i.RR=k})(window,document,RightJS);
@@ -0,0 +1,1461 @@
1
+ /**
2
+ * The calendar widget implemented with RightJS
3
+ *
4
+ * Home page: http://rightjs.org/ui/calendar
5
+ *
6
+ * @copyright (C) 2009-2010 Nikolay Nemshilov
7
+ */
8
+ var Calendar = RightJS.Calendar = (function(document, parseInt, RightJS) {
9
+ /**
10
+ * This module defines the basic widgets constructor
11
+ * it creates an abstract proxy with the common functionality
12
+ * which then we reuse and override in the actual widgets
13
+ *
14
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
15
+ */
16
+
17
+ /**
18
+ * The filenames to include
19
+ *
20
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
21
+ */
22
+
23
+ var R = RightJS,
24
+ $ = RightJS.$,
25
+ $$ = RightJS.$$,
26
+ $w = RightJS.$w,
27
+ $ext = RightJS.$ext,
28
+ $uid = RightJS.$uid,
29
+ isString = RightJS.isString,
30
+ isArray = RightJS.isArray,
31
+ isFunction = RightJS.isFunction,
32
+ Class = RightJS.Class,
33
+ Element = RightJS.Element,
34
+ Input = RightJS.Input,
35
+ RegExp = RightJS.RegExp,
36
+ Browser = RightJS.Browser;
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+
46
+ /**
47
+ * The widget units constructor
48
+ *
49
+ * @param String tag-name or Object methods
50
+ * @param Object methods
51
+ * @return Widget wrapper
52
+ */
53
+ function Widget(tag_name, methods) {
54
+ if (!methods) {
55
+ methods = tag_name;
56
+ tag_name = 'DIV';
57
+ }
58
+
59
+ /**
60
+ * An Abstract Widget Unit
61
+ *
62
+ * Copyright (C) 2010 Nikolay Nemshilov
63
+ */
64
+ var AbstractWidget = new RightJS.Class(RightJS.Element.Wrappers[tag_name] || RightJS.Element, {
65
+ /**
66
+ * The common constructor
67
+ *
68
+ * @param Object options
69
+ * @param String optional tag name
70
+ * @return void
71
+ */
72
+ initialize: function(key, options) {
73
+ this.key = key;
74
+ var args = [{'class': 'rui-' + key}];
75
+
76
+ // those two have different constructors
77
+ if (!(this instanceof RightJS.Input || this instanceof RightJS.Form)) {
78
+ args.unshift(tag_name);
79
+ }
80
+ this.$super.apply(this, args);
81
+
82
+ if (RightJS.isString(options)) {
83
+ options = RightJS.$(options);
84
+ }
85
+
86
+ // if the options is another element then
87
+ // try to dynamically rewrap it with our widget
88
+ if (options instanceof RightJS.Element) {
89
+ this._ = options._;
90
+ if ('$listeners' in options) {
91
+ options.$listeners = options.$listeners;
92
+ }
93
+ options = {};
94
+ }
95
+ this.setOptions(options, this);
96
+
97
+ return (RightJS.Wrapper.Cache[RightJS.$uid(this._)] = this);
98
+ },
99
+
100
+ // protected
101
+
102
+ /**
103
+ * Catches the options
104
+ *
105
+ * @param Object user-options
106
+ * @param Element element with contextual options
107
+ * @return void
108
+ */
109
+ setOptions: function(options, element) {
110
+ element = element || this;
111
+ RightJS.Options.setOptions.call(this,
112
+ RightJS.Object.merge(options, eval("("+(
113
+ element.get('data-'+ this.key) || '{}'
114
+ )+")"))
115
+ );
116
+ return this;
117
+ }
118
+ });
119
+
120
+ /**
121
+ * Creating the actual widget class
122
+ *
123
+ */
124
+ var Klass = new RightJS.Class(AbstractWidget, methods);
125
+
126
+ // creating the widget related shortcuts
127
+ RightJS.Observer.createShortcuts(Klass.prototype, Klass.EVENTS || []);
128
+
129
+ return Klass;
130
+ }
131
+
132
+
133
+ /**
134
+ * A shared button unit.
135
+ * NOTE: we use the DIV units instead of INPUTS
136
+ * so those buttons didn't interfere with
137
+ * the user's tab-index on his page
138
+ *
139
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
140
+ */
141
+ var Button = new RightJS.Class(RightJS.Element, {
142
+ /**
143
+ * Constructor
144
+ *
145
+ * @param String caption
146
+ * @param Object options
147
+ * @return void
148
+ */
149
+ initialize: function(caption, options) {
150
+ this.$super('div', options);
151
+ this._.innerHTML = caption;
152
+ this.addClass('rui-button');
153
+ this.on('selectstart', 'stopEvent');
154
+ },
155
+
156
+ /**
157
+ * Disasbles the button
158
+ *
159
+ * @return Button this
160
+ */
161
+ disable: function() {
162
+ return this.addClass('rui-button-disabled');
163
+ },
164
+
165
+ /**
166
+ * Enables the button
167
+ *
168
+ * @return Button this
169
+ */
170
+ enable: function() {
171
+ return this.removeClass('rui-button-disabled');
172
+ },
173
+
174
+ /**
175
+ * Checks if the button is disabled
176
+ *
177
+ * @return Button this
178
+ */
179
+ disabled: function() {
180
+ return this.hasClass('rui-button-disabled');
181
+ },
182
+
183
+ /**
184
+ * Checks if the button is enabled
185
+ *
186
+ * @return Button this
187
+ */
188
+ enabled: function() {
189
+ return !this.disabled();
190
+ },
191
+
192
+ /**
193
+ * Overloading the method, so it fired the events
194
+ * only when the button is active
195
+ *
196
+ * @return Button this
197
+ */
198
+ fire: function() {
199
+ if (this.enabled()) {
200
+ this.$super.apply(this, arguments);
201
+ }
202
+ return this;
203
+ }
204
+ });
205
+
206
+
207
+ /**
208
+ * A shared module that toggles a widget visibility status
209
+ * in a uniformed way according to the options settings
210
+ *
211
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
212
+ */
213
+ var Toggler = {
214
+ /**
215
+ * Shows the element
216
+ *
217
+ * @param String fx-name
218
+ * @param Object fx-options
219
+ * @return Element this
220
+ */
221
+ show: function(fx_name, fx_options) {
222
+ this.constructor.current = this;
223
+ return Toggler_toggle(this, 'show', fx_name, fx_options);
224
+ },
225
+
226
+ /**
227
+ * Hides the element
228
+ *
229
+ * @param String fx-name
230
+ * @param Object fx-options
231
+ * @return Element this
232
+ */
233
+ hide: function(fx_name, fx_options) {
234
+ this.constructor.current = null;
235
+ return Toggler_toggle(this, 'show', fx_name, fx_options);
236
+ },
237
+
238
+ /**
239
+ * Toggles the widget at the given element
240
+ *
241
+ * @param Element the related element
242
+ * @param String position right/bottom (bottom is the default)
243
+ * @param Boolean marker if the element should be resized to the element size
244
+ * @return Widget this
245
+ */
246
+ showAt: function(element, where, resize) {
247
+ this.hide(null).shownAt = element = RightJS.$(element);
248
+
249
+ // moves this element at the given one
250
+ Toggler_re_position.call(this, element, where, resize);
251
+
252
+ return this.show();
253
+ },
254
+
255
+ /**
256
+ * Toggles the widget at the given element
257
+ *
258
+ * @param Element the related element
259
+ * @param String position top/left/right/bottom (bottom is the default)
260
+ * @param Boolean marker if the element should be resized to the element size
261
+ * @return Widget this
262
+ */
263
+ toggleAt: function(element, where, resize) {
264
+ return this.hidden() ? this.showAt(element, where, resize) : this.hide();
265
+ }
266
+ };
267
+
268
+
269
+ /**
270
+ * toggles the element's state according to the current settings
271
+ *
272
+ * @param event String 'show' or 'hide' the event name
273
+ * @param String an optional fx-name
274
+ * @param Object an optional fx-options hash
275
+ * @return void
276
+ */
277
+ function Toggler_toggle(element, event, fx_name, fx_options) {
278
+ if (RightJS.Fx) {
279
+ if (fx_name === undefined) {
280
+ fx_name = element.options.fxName;
281
+
282
+ if (fx_options === undefined) {
283
+ fx_options = {
284
+ duration: element.options.fxDuration,
285
+ onFinish: RightJS(element.fire).bind(element, event)
286
+ };
287
+
288
+ // hide on double time
289
+ if (event === 'hide') {
290
+ fx_options.duration = (RightJS.Fx.Durations[fx_options.duration] ||
291
+ fx_options.duration) / 2;
292
+ }
293
+ }
294
+ }
295
+ }
296
+
297
+ // manually trigger the event if no fx were specified
298
+ if (!RightJS.Fx || !fx_name) { element.fire(event); }
299
+
300
+ return element.$super(fx_name, fx_options);
301
+ }
302
+
303
+ /**
304
+ * Relatively positions the current element
305
+ * against the specified one
306
+ *
307
+ * NOTE: this function is called in a context
308
+ * of another element
309
+ *
310
+ * @param Element the target element
311
+ * @param String position 'right' or 'bottom'
312
+ * @param Boolean if `true` then the element size will be adjusted
313
+ * @return void
314
+ */
315
+ function Toggler_re_position(element, where, resize) {
316
+ var anchor = this.reAnchor || (this.reAnchor =
317
+ new RightJS.Element('div', {'class': 'rui-re-anchor'}))
318
+ .insert(this),
319
+
320
+ pos = anchor.insertTo(element, 'after').position(),
321
+ dims = element.dimensions(), target = this,
322
+
323
+ border_top = parseInt(element.getStyle('borderTopWidth')),
324
+ border_left = parseInt(element.getStyle('borderLeftWidth')),
325
+ border_right = parseInt(element.getStyle('borderRightWidth')),
326
+ border_bottom = parseInt(element.getStyle('borderBottomWidth')),
327
+
328
+ top = dims.top - pos.y + border_top,
329
+ left = dims.left - pos.x + border_left,
330
+ width = dims.width - border_left - border_right,
331
+ height = dims.height - border_top - border_bottom;
332
+
333
+ // making the element to appear so we could read it's sizes
334
+ target.setStyle('visibility:hidden').show(null);
335
+
336
+ if (where === 'right') {
337
+ left += width - target.size().x;
338
+ } else { // bottom
339
+ top += height;
340
+ }
341
+
342
+ target.moveTo(left, top);
343
+
344
+ if (resize) {
345
+ if (where === 'left' || where === 'right') {
346
+ target.setHeight(height);
347
+ } else {
348
+ target.setWidth(width);
349
+ }
350
+ }
351
+
352
+ // rolling the invisibility back
353
+ target.setStyle('visibility:visible').hide(null);
354
+ }
355
+
356
+ /**
357
+ * A shared module that provides for the widgets an ability
358
+ * to be assigned to an input element and work in pair with it
359
+ *
360
+ * NOTE: this module works in pair with the 'RePosition' module!
361
+ *
362
+ * Copyright (C) 2010 Nikolay Nemshilov
363
+ */
364
+ var Assignable = {
365
+ /**
366
+ * Assigns the widget to serve the given input element
367
+ *
368
+ * Basically it puts the references of the current widget
369
+ * to the input and trigger objects so they could be recognized
370
+ * later, and it also synchronizes the changes between the input
371
+ * element and the widget
372
+ *
373
+ * @param {Element} input field
374
+ * @param {Element} optional trigger
375
+ * @return Widget this
376
+ */
377
+ assignTo: function(input, trigger) {
378
+ input = RightJS.$(input);
379
+ trigger = RightJS.$(trigger);
380
+
381
+ if (trigger) {
382
+ trigger[this.key] = this;
383
+ trigger.assignedInput = input;
384
+ } else {
385
+ input[this.key] = this;
386
+ }
387
+
388
+ var on_change = RightJS(function() {
389
+ if (this.visible() && (!this.showAt || this.shownAt === input)) {
390
+ this.setValue(input.value());
391
+ }
392
+ }).bind(this);
393
+
394
+ input.on({
395
+ keyup: on_change,
396
+ change: on_change
397
+ });
398
+
399
+ this.onChange(function() {
400
+ if (!this.showAt || this.shownAt === input) {
401
+ input.setValue(this.getValue());
402
+ }
403
+ });
404
+
405
+ return this;
406
+ }
407
+ };
408
+
409
+
410
+ /**
411
+ * Converts a number into a string with leading zeros
412
+ *
413
+ * @param Number number
414
+ * @return String with zeros
415
+ */
416
+ function zerofy(number) {
417
+ return (number < 10 ? '0' : '') + number;
418
+ }
419
+
420
+
421
+ /**
422
+ * The calendar widget for RightJS
423
+ *
424
+ * Copyright (C) 2009-2011 Nikolay Nemshilov
425
+ */
426
+ var Calendar = new Widget({
427
+ include: [Toggler, Assignable],
428
+
429
+ extend: {
430
+ version: '2.2.0',
431
+
432
+ EVENTS: $w('show hide change done'),
433
+
434
+ Options: {
435
+ format: 'ISO', // a key out of the predefined formats or a format string
436
+
437
+ showTime: null, // null for automatic, or true|false to enforce
438
+ showButtons: false, // show the bottom buttons
439
+
440
+ minDate: false, // the minimal date available
441
+ maxDate: false, // the maximal date available
442
+
443
+ fxName: 'fade', // set to null if you don't wanna any fx
444
+ fxDuration: 'short', // the fx-duration
445
+
446
+ firstDay: 1, // 1 for Monday, 0 for Sunday
447
+ numberOfMonths: 1, // a number or [x, y] greed definition
448
+ timePeriod: 1, // the timepicker minimal periods (in minutes, might be bigger than 60)
449
+
450
+ twentyFourHour: null, // null for automatic, or true|false to enforce
451
+ listYears: false, // show/hide the years listing buttons
452
+
453
+ hideOnPick: false, // hides the popup when the user changes a day
454
+
455
+ update: null, // a reference to an input element to assign to
456
+ trigger: null, // a reference to a trigger element that would be paired too
457
+
458
+ cssRule: '*[data-calendar]' // css rule for calendar related elements
459
+ },
460
+
461
+ Formats: {
462
+ ISO: '%Y-%m-%d',
463
+ POSIX: '%Y/%m/%d',
464
+ EUR: '%d-%m-%Y',
465
+ US: '%m/%d/%Y'
466
+ },
467
+
468
+ i18n: {
469
+ Done: 'Done',
470
+ Now: 'Now',
471
+ NextMonth: 'Next Month',
472
+ PrevMonth: 'Previous Month',
473
+ NextYear: 'Next Year',
474
+ PrevYear: 'Previous Year',
475
+
476
+ dayNames: $w('Sunday Monday Tuesday Wednesday Thursday Friday Saturday'),
477
+ dayNamesShort: $w('Sun Mon Tue Wed Thu Fri Sat'),
478
+ dayNamesMin: $w('Su Mo Tu We Th Fr Sa'),
479
+ monthNames: $w('January February March April May June July August September October November December'),
480
+ monthNamesShort: $w('Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec')
481
+ },
482
+
483
+ current: null,
484
+
485
+ // hides all the popup calendars
486
+ hideAll: function(that_one) {
487
+ $$('div.rui-calendar').each(function(element) {
488
+ if (element instanceof Calendar && element !== that_one && element.visible() && !element.inlined()) {
489
+ element.hide();
490
+ }
491
+ });
492
+ }
493
+ },
494
+
495
+ /**
496
+ * Basic constructor
497
+ *
498
+ * @param Object options
499
+ */
500
+ initialize: function(options) {
501
+ this.$super('calendar', options);
502
+ this.addClass('rui-panel');
503
+
504
+ options = this.options;
505
+
506
+ this.insert([
507
+ this.swaps = new Swaps(options),
508
+ this.greed = new Greed(options)
509
+ ]);
510
+
511
+ if (options.showTime) {
512
+ this.insert(this.timepicker = new Timepicker(options));
513
+ }
514
+
515
+ if (options.showButtons) {
516
+ this.insert(this.buttons = new Buttons(options));
517
+ }
518
+
519
+ this.setDate(new Date()).initEvents();
520
+ },
521
+
522
+ /**
523
+ * Sets the date on the calendar
524
+ *
525
+ * NOTE: if it's `true` then it will change the date but
526
+ * won't shift the months greed (used in the days picking)
527
+ *
528
+ * @param Date date or String date
529
+ * @param Boolean no-shifting mode
530
+ * @return Calendar this
531
+ */
532
+ setDate: function(date, no_shift) {
533
+ if ((date = this.parse(date))) {
534
+ var options = this.options;
535
+
536
+ // checking the date range constrains
537
+ if (options.minDate && options.minDate > date) {
538
+ date = new Date(options.minDate);
539
+ }
540
+ if (options.maxDate && options.maxDate < date) {
541
+ date = new Date(options.maxDate);
542
+ date.setDate(date.getDate() - 1);
543
+ }
544
+
545
+ // setting the dates greed
546
+ this._date = no_shift ? new Date(this._date || this.date) : null;
547
+ this.greed.setDate(this._date || date, date);
548
+
549
+ // updating the shifters state
550
+ if (options.minDate || options.maxDate) {
551
+ this.swaps.setDate(date);
552
+ }
553
+
554
+ // updating the time-picker
555
+ if (this.timepicker && !no_shift) {
556
+ this.timepicker.setDate(date);
557
+ }
558
+
559
+ if (date != this.date) {
560
+ this.fire('change', {date: this.date = date});
561
+ }
562
+ }
563
+
564
+ return this;
565
+ },
566
+
567
+ /**
568
+ * Returns the current date on the calendar
569
+ *
570
+ * @return Date currently selected date on the calendar
571
+ */
572
+ getDate: function() {
573
+ return this.date;
574
+ },
575
+
576
+ /**
577
+ * Sets the value as a string
578
+ *
579
+ * @param String value
580
+ * @return Calendar this
581
+ */
582
+ setValue: function(value) {
583
+ return this.setDate(value);
584
+ },
585
+
586
+ /**
587
+ * Returns the value as a string
588
+ *
589
+ * @param String optional format
590
+ * @return String formatted date
591
+ */
592
+ getValue: function(format) {
593
+ return this.format(format);
594
+ },
595
+
596
+ /**
597
+ * Inserts the calendar into the element making it inlined
598
+ *
599
+ * @param Element element or String element id
600
+ * @param String optional position top/bottom/before/after/instead, 'bottom' is default
601
+ * @return Calendar this
602
+ */
603
+ insertTo: function(element, position) {
604
+ this.addClass('rui-calendar-inline');
605
+ return this.$super(element, position);
606
+ },
607
+
608
+ /**
609
+ * Marks it done
610
+ *
611
+ * @return Calendar this
612
+ */
613
+ done: function() {
614
+ if (!this.inlined()) {
615
+ this.hide();
616
+ }
617
+
618
+ this.fire('done', {date: this.date});
619
+ },
620
+
621
+ /**
622
+ * Checks if the calendar is inlined
623
+ *
624
+ * @return boolean check
625
+ */
626
+ inlined: function() {
627
+ return this.hasClass('rui-calendar-inline');
628
+ },
629
+
630
+ // protected
631
+
632
+ /**
633
+ * additional options processing
634
+ *
635
+ * @param Object options
636
+ * @return Calendar this
637
+ */
638
+ setOptions: function(user_options) {
639
+ user_options = user_options || {};
640
+ this.$super(user_options, $(user_options.trigger || user_options.update));
641
+
642
+ var klass = this.constructor, options = this.options;
643
+
644
+ // merging the i18n tables
645
+ options.i18n = {};
646
+
647
+ for (var key in klass.i18n) {
648
+ options.i18n[key] = isArray(klass.i18n[key]) ? klass.i18n[key].clone() : klass.i18n[key];
649
+ }
650
+ $ext(options.i18n, user_options.i18n);
651
+
652
+ // defining the current days sequence
653
+ options.dayNames = options.i18n.dayNamesMin;
654
+ if (options.firstDay) {
655
+ options.dayNames.push(options.dayNames.shift());
656
+ }
657
+
658
+ // the monthes table cleaning up
659
+ if (!isArray(options.numberOfMonths)) {
660
+ options.numberOfMonths = [options.numberOfMonths, 1];
661
+ }
662
+
663
+ // min/max dates preprocessing
664
+ if (options.minDate) {
665
+ options.minDate = this.parse(options.minDate);
666
+ }
667
+ if (options.maxDate) {
668
+ options.maxDate = this.parse(options.maxDate);
669
+ options.maxDate.setDate(options.maxDate.getDate() + 1);
670
+ }
671
+
672
+ // format catching up
673
+ options.format = R(klass.Formats[options.format] || options.format).trim();
674
+
675
+ // setting up the showTime option
676
+ if (options.showTime === null) {
677
+ options.showTime = options.format.search(/%[HkIl]/) > -1;
678
+ }
679
+
680
+ // setting up the 24-hours format
681
+ if (options.twentyFourHour === null) {
682
+ options.twentyFourHour = options.format.search(/%[Il]/) < 0;
683
+ }
684
+
685
+ // enforcing the 24 hours format if the time threshold is some weird number
686
+ if (options.timePeriod > 60 && 12 % Math.ceil(options.timePeriod/60)) {
687
+ options.twentyFourHour = true;
688
+ }
689
+
690
+ if (options.update) {
691
+ this.assignTo(options.update, options.trigger);
692
+ }
693
+
694
+ return this;
695
+ },
696
+
697
+ /**
698
+ * hides all the other calendars on the page
699
+ *
700
+ * @return Calendar this
701
+ */
702
+ hideOthers: function() {
703
+ Calendar.hideAll(this);
704
+ return this;
705
+ }
706
+ });
707
+
708
+
709
+ /**
710
+ * The calendar month/year swapping buttons block
711
+ *
712
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
713
+ */
714
+ var Swaps = new Class(Element, {
715
+ /**
716
+ * Constructor
717
+ *
718
+ * @param Object options
719
+ * @return void
720
+ */
721
+ initialize: function(options) {
722
+ this.$super('div', {'class': 'swaps'});
723
+ this.options = options;
724
+
725
+ var i18n = options.i18n;
726
+
727
+ this.insert([
728
+ this.prevMonth = new Button('&lsaquo;', {title: i18n.PrevMonth, 'class': 'prev-month'}),
729
+ this.nextMonth = new Button('&rsaquo;', {title: i18n.NextMonth, 'class': 'next-month'})
730
+ ]);
731
+
732
+ if (options.listYears) {
733
+ this.insert([
734
+ this.prevYear = new Button('&laquo;', {title: i18n.PrevYear, 'class': 'prev-year'}),
735
+ this.nextYear = new Button('&raquo;', {title: i18n.NextYear, 'class': 'next-year'})
736
+ ]);
737
+ }
738
+
739
+ this.buttons = R([this.prevMonth, this.nextMonth, this.prevYear, this.nextYear]).compact();
740
+
741
+ this.onClick(this.clicked);
742
+ },
743
+
744
+ /**
745
+ * Changes the swapping buttons state depending on the options and the current date
746
+ *
747
+ * @param Date date
748
+ * @return void
749
+ */
750
+ setDate: function(date) {
751
+ var options = this.options, months_num = options.numberOfMonths[0] * options.numberOfMonths[1],
752
+ has_prev_year = true, has_next_year = true, has_prev_month = true, has_next_month = true;
753
+
754
+ if (options.minDate) {
755
+ var beginning = new Date(date.getFullYear(),0,1,0,0,0);
756
+ var min_date = new Date(options.minDate.getFullYear(),0,1,0,0,0);
757
+
758
+ has_prev_year = beginning > min_date;
759
+
760
+ beginning.setMonth(date.getMonth() - Math.ceil(months_num - months_num/2));
761
+ min_date.setMonth(options.minDate.getMonth());
762
+
763
+ has_prev_month = beginning >= min_date;
764
+ }
765
+
766
+ if (options.maxDate) {
767
+ var end = new Date(date);
768
+ var max_date = new Date(options.maxDate);
769
+ var dates = R([end, max_date]);
770
+ dates.each(function(date) {
771
+ date.setDate(32);
772
+ date.setMonth(date.getMonth() - 1);
773
+ date.setDate(32 - date.getDate());
774
+ date.setHours(0);
775
+ date.setMinutes(0);
776
+ date.setSeconds(0);
777
+ date.setMilliseconds(0);
778
+ });
779
+
780
+ has_next_month = end < max_date;
781
+
782
+ // checking the next year
783
+ dates.each('setMonth', 0);
784
+ has_next_year = end < max_date;
785
+ }
786
+
787
+ this.nextMonth[has_next_month ? 'enable':'disable']();
788
+ this.prevMonth[has_prev_month ? 'enable':'disable']();
789
+
790
+ if (this.nextYear) {
791
+ this.nextYear[has_next_year ? 'enable':'disable']();
792
+ this.prevYear[has_prev_year ? 'enable':'disable']();
793
+ }
794
+ },
795
+
796
+ // protected
797
+
798
+ // handles the clicks on the
799
+ clicked: function(event) {
800
+ var target = event.target;
801
+ if (target && this.buttons.include(target)) {
802
+ if (target.enabled()) {
803
+ this.fire(target.get('className').split(/\s+/)[0]);
804
+ }
805
+ }
806
+ }
807
+ });
808
+
809
+
810
+ /**
811
+ * Represents a single month block
812
+ *
813
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
814
+ */
815
+ var Month = new Class(Element, {
816
+ /**
817
+ * Constructor
818
+ *
819
+ * @param Object options
820
+ * @return void
821
+ */
822
+ initialize: function(options) {
823
+ this.$super('table', {'class': 'month'});
824
+ this.options = options;
825
+
826
+ // the caption (for the month name)
827
+ this.insert(this.caption = new Element('caption'));
828
+
829
+ // the headline for the day-names
830
+ this.insert('<thead><tr>'+
831
+ options.dayNames.map(function(name) {return '<th>'+ name +'</th>';}).join('') +
832
+ '</tr></thead>');
833
+
834
+ // the body with the day-cells
835
+ this.days = [];
836
+
837
+ var tbody = new Element('tbody').insertTo(this), x, y, row;
838
+
839
+ for (y=0; y < 6; y++) {
840
+ row = new Element('tr').insertTo(tbody);
841
+ for (x=0; x < 7; x++) {
842
+ this.days.push(new Element('td').insertTo(row));
843
+ }
844
+ }
845
+
846
+ this.onClick(this.clicked);
847
+ },
848
+
849
+ /**
850
+ * Initializes the month values by the date
851
+ *
852
+ * @param Date date
853
+ * @return void
854
+ */
855
+ setDate: function(date, current_date) {
856
+ // getting the number of days in the month
857
+ date.setDate(32);
858
+ var days_number = 32 - date.getDate();
859
+ date.setMonth(date.getMonth()-1);
860
+
861
+ var cur_day = Math.ceil(current_date.getTime() / 86400000),
862
+ options = this.options, i18n = options.i18n, days = this.days;
863
+
864
+ // resetting the first and last two weeks cells
865
+ // because there will be some empty cells over there
866
+ for (var i=0, len = days.length-1, one, two, tre; i < 7; i++) {
867
+ one = days[i]._;
868
+ two = days[len - i]._;
869
+ tre = days[len - i - 7]._;
870
+
871
+ one.innerHTML = two.innerHTML = tre.innerHTML = '';
872
+ one.className = two.className = tre.className = 'blank';
873
+ }
874
+
875
+ // putting the actual day numbers in place
876
+ for (var i=1, row=0, week, cell; i <= days_number; i++) {
877
+ date.setDate(i);
878
+ var day_num = date.getDay();
879
+
880
+ if (options.firstDay === 1) { day_num = day_num > 0 ? day_num-1 : 6; }
881
+ if (i === 1 || day_num === 0) {
882
+ week = days.slice(row*7, row*7 + 7); row ++;
883
+ }
884
+
885
+ cell = week[day_num]._;
886
+
887
+ if (Browser.OLD) { // IE6 has a nasty glitch with that
888
+ cell.innerHTML = '';
889
+ cell.appendChild(document.createTextNode(i));
890
+ } else {
891
+ cell.innerHTML = ''+i;
892
+ }
893
+
894
+ cell.className = cur_day === Math.ceil(date.getTime() / 86400000) ? 'selected' : '';
895
+
896
+ if ((options.minDate && options.minDate > date) || (options.maxDate && options.maxDate < date)) {
897
+ cell.className = 'disabled';
898
+ }
899
+
900
+ week[day_num].date = new Date(date);
901
+ }
902
+
903
+ // setting up the caption with the month name
904
+ var caption = (options.listYears ?
905
+ i18n.monthNamesShort[date.getMonth()] + ',' :
906
+ i18n.monthNames[date.getMonth()])+
907
+ ' '+date.getFullYear(),
908
+ element = this.caption._;
909
+
910
+ if (Browser.OLD) {
911
+ element.innerHTML = '';
912
+ element.appendChild(document.createTextNode(caption));
913
+ } else {
914
+ element.innerHTML = caption;
915
+ }
916
+ },
917
+
918
+ // protected
919
+
920
+ /**
921
+ * Handles clicks on the day-cells
922
+ *
923
+ * @param Event click event
924
+ * @return void
925
+ */
926
+ clicked: function(event) {
927
+ var target = event.target, date = target.date;
928
+
929
+ if (target && date && !target.hasClass('disabled') && !target.hasClass('blank')) {
930
+ target.addClass('selected');
931
+
932
+ this.fire('date-set', {
933
+ date: date.getDate(),
934
+ month: date.getMonth(),
935
+ year: date.getFullYear()
936
+ });
937
+ }
938
+ }
939
+ });
940
+
941
+
942
+ /**
943
+ * The calendar months greed unit
944
+ *
945
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
946
+ */
947
+ var Greed = new Class(Element, {
948
+ /**
949
+ * Constructor
950
+ *
951
+ * @param Object options
952
+ * @return void
953
+ */
954
+ initialize: function(options) {
955
+ this.$super('table', {'class': 'greed'});
956
+
957
+ this.months = [];
958
+
959
+ var tbody = new Element('tbody').insertTo(this), month;
960
+
961
+ for (var y=0; y < options.numberOfMonths[1]; y++) {
962
+ var row = new Element('tr').insertTo(tbody);
963
+ for (var x=0; x < options.numberOfMonths[0]; x++) {
964
+ this.months.push(month = new Month(options));
965
+ new Element('td').insertTo(row).insert(month);
966
+ }
967
+ }
968
+ },
969
+
970
+ /**
971
+ * Sets the months to the date
972
+ *
973
+ * @param Date date in the middle of the greed
974
+ * @param the current date (might be different)
975
+ * @return void
976
+ */
977
+ setDate: function(date, current_date) {
978
+ var months = this.months, months_num = months.length;
979
+
980
+ current_date = current_date || date;
981
+
982
+ for (var i=-Math.ceil(months_num - months_num/2)+1,j=0; i < Math.floor(months_num - months_num/2)+1; i++,j++) {
983
+ var month_date = new Date(date);
984
+ month_date.setMonth(date.getMonth() + i);
985
+ months[j].setDate(month_date, current_date);
986
+ }
987
+ }
988
+ });
989
+
990
+
991
+ /**
992
+ * The time-picker block unit
993
+ *
994
+ * Copyright (C) 2010-2011 Nikolay Nemshilov
995
+ */
996
+ var Timepicker = new Class(Element, {
997
+ /**
998
+ * Constructor
999
+ *
1000
+ * @param Object options
1001
+ * @return void
1002
+ */
1003
+ initialize: function(options) {
1004
+ this.$super('div', {'class': 'timepicker'});
1005
+ this.options = options;
1006
+
1007
+ var on_change = R(this.timeChanged).bind(this);
1008
+
1009
+ this.insert([
1010
+ this.hours = new Element('select').onChange(on_change),
1011
+ this.minutes = new Element('select').onChange(on_change)
1012
+ ]);
1013
+
1014
+ var minutes_threshold = options.timePeriod < 60 ? options.timePeriod : 60;
1015
+ var hours_threshold = options.timePeriod < 60 ? 1 : Math.ceil(options.timePeriod / 60);
1016
+
1017
+ for (var i=0; i < 60; i++) {
1018
+ var caption = zerofy(i);
1019
+
1020
+ if (i < 24 && i % hours_threshold == 0) {
1021
+ if (options.twentyFourHour) {
1022
+ this.hours.insert(new Element('option', {value: i, html: caption}));
1023
+ } else if (i < 12) {
1024
+ this.hours.insert(new Element('option', {value: i, html: i == 0 ? 12 : i}));
1025
+ }
1026
+ }
1027
+
1028
+ if (i % minutes_threshold == 0) {
1029
+ this.minutes.insert(new Element('option', {value: i, html: caption}));
1030
+ }
1031
+ }
1032
+
1033
+
1034
+ // adding the meridian picker if it's a 12 am|pm picker
1035
+ if (!options.twentyFourHour) {
1036
+ this.meridian = new Element('select').onChange(on_change).insertTo(this);
1037
+
1038
+ R(R(options.format).includes(/%P/) ? ['am', 'pm'] : ['AM', 'PM']).each(function(value) {
1039
+ this.meridian.insert(new Element('option', {value: value.toLowerCase(), html: value}));
1040
+ }, this);
1041
+ }
1042
+ },
1043
+
1044
+ /**
1045
+ * Sets the time-picker values by the data
1046
+ *
1047
+ * @param Date date
1048
+ * @return void
1049
+ */
1050
+ setDate: function(date) {
1051
+ var options = this.options;
1052
+ var hour = options.timePeriod < 60 ? date.getHours() :
1053
+ Math.round(date.getHours()/(options.timePeriod/60)) * (options.timePeriod/60);
1054
+ var minute = Math.round(date.getMinutes() / (options.timePeriod % 60)) * options.timePeriod;
1055
+
1056
+ if (this.meridian) {
1057
+ this.meridian.setValue(hour < 12 ? 'am' : 'pm');
1058
+ hour = (hour == 0 || hour == 12) ? 12 : hour > 12 ? (hour - 12) : hour;
1059
+ }
1060
+
1061
+ this.hours.setValue(hour);
1062
+ this.minutes.setValue(minute);
1063
+ },
1064
+
1065
+ // protected
1066
+
1067
+ /**
1068
+ * Handles the time-picking events
1069
+ *
1070
+ * @return void
1071
+ */
1072
+ timeChanged: function(event) {
1073
+ event.stopPropagation();
1074
+
1075
+ var hours = parseInt(this.hours.value());
1076
+ var minutes = parseInt(this.minutes.value());
1077
+
1078
+ if (this.meridian) {
1079
+ if (hours == 12) {
1080
+ hours = 0;
1081
+ }
1082
+ if (this.meridian.value() == 'pm') {
1083
+ hours += 12;
1084
+ }
1085
+ }
1086
+
1087
+ this.fire('time-set', {hours: hours, minutes: minutes});
1088
+ }
1089
+ });
1090
+
1091
+
1092
+ /**
1093
+ * The bottom-buttons block unit
1094
+ *
1095
+ * Copyright (C) 2010 Nikolay Nemshilov
1096
+ */
1097
+ var Buttons = new Class(Element, {
1098
+ /**
1099
+ * Constructor
1100
+ *
1101
+ * @param Object options
1102
+ * @return void
1103
+ */
1104
+ initialize: function(options) {
1105
+ this.$super('div', {'class': 'buttons'});
1106
+
1107
+ this.insert([
1108
+ new Button(options.i18n.Now, {'class': 'now'}).onClick('fire', 'now-clicked'),
1109
+ new Button(options.i18n.Done, {'class': 'done'}).onClick('fire', 'done-clicked')
1110
+ ]);
1111
+ }
1112
+ });
1113
+
1114
+
1115
+ /**
1116
+ * This module handles the dates parsing/formatting processes
1117
+ *
1118
+ * To format dates and times this scripts use the GNU (C/Python/Ruby) strftime
1119
+ * function formatting principles
1120
+ *
1121
+ * %a - The abbreviated weekday name (``Sun'')
1122
+ * %A - The full weekday name (``Sunday'')
1123
+ * %b - The abbreviated month name (``Jan'')
1124
+ * %B - The full month name (``January'')
1125
+ * %d - Day of the month (01..31)
1126
+ * %e - Day of the month without leading zero (1..31)
1127
+ * %m - Month of the year (01..12)
1128
+ * %y - Year without a century (00..99)
1129
+ * %Y - Year with century
1130
+ * %H - Hour of the day, 24-hour clock (00..23)
1131
+ * %k - Hour of the day, 24-hour clock without leading zero (0..23)
1132
+ * %I - Hour of the day, 12-hour clock (01..12)
1133
+ * %l - Hour of the day, 12-hour clock without leading zer (0..12)
1134
+ * %p - Meridian indicator (``AM'' or ``PM'')
1135
+ * %P - Meridian indicator (``pm'' or ``pm'')
1136
+ * %M - Minute of the hour (00..59)
1137
+ * %S - Second of the minute (00..60)
1138
+ * %% - Literal ``%'' character
1139
+ *
1140
+ * Copyright (C) 2009-2010 Nikolay V. Nemshilov
1141
+ */
1142
+ Calendar.include({
1143
+
1144
+ /**
1145
+ * Parses out the given string based on the current date formatting
1146
+ *
1147
+ * @param String string date
1148
+ * @return Date parsed date or null if it wasn't parsed
1149
+ */
1150
+ parse: function(string) {
1151
+ var date;
1152
+
1153
+ if (isString(string) && string) {
1154
+ var tpl = RegExp.escape(this.options.format);
1155
+ var holders = R(tpl.match(/%[a-z]/ig)).map('match', /[a-z]$/i).map('first').without('%');
1156
+ var re = new RegExp('^'+tpl.replace(/%p/i, '(pm|PM|am|AM)').replace(/(%[a-z])/ig, '(.+?)')+'$');
1157
+
1158
+ var match = R(string).trim().match(re);
1159
+
1160
+ if (match) {
1161
+ match.shift();
1162
+
1163
+ var year = null, month = null, hour = null, minute = null, second = null, meridian;
1164
+
1165
+ while (match.length) {
1166
+ var value = match.shift();
1167
+ var key = holders.shift();
1168
+
1169
+ if (key.toLowerCase() == 'b') {
1170
+ month = this.options.i18n[key=='b' ? 'monthNamesShort' : 'monthNames'].indexOf(value);
1171
+ } else if (key.toLowerCase() == 'p') {
1172
+ meridian = value.toLowerCase();
1173
+ } else {
1174
+ value = parseInt(value, 10);
1175
+ switch(key) {
1176
+ case 'd':
1177
+ case 'e': date = value; break;
1178
+ case 'm': month = value-1; break;
1179
+ case 'y':
1180
+ case 'Y': year = value; break;
1181
+ case 'H':
1182
+ case 'k':
1183
+ case 'I':
1184
+ case 'l': hour = value; break;
1185
+ case 'M': minute = value; break;
1186
+ case 'S': second = value; break;
1187
+ }
1188
+ }
1189
+ }
1190
+
1191
+ // converting 1..12am|pm into 0..23 hours marker
1192
+ if (meridian) {
1193
+ hour = hour == 12 ? 0 : hour;
1194
+ hour = (meridian == 'pm' ? hour + 12 : hour);
1195
+ }
1196
+
1197
+ date = new Date(year, month, date, hour, minute, second);
1198
+ }
1199
+ } else if (string instanceof Date || Date.parse(string)) {
1200
+ date = new Date(string);
1201
+ }
1202
+
1203
+ return (!date || isNaN(date.getTime())) ? null : date;
1204
+ },
1205
+
1206
+ /**
1207
+ * Formats the current date into a string depend on the current or given format
1208
+ *
1209
+ * @param String optional format
1210
+ * @return String formatted data
1211
+ */
1212
+ format: function(format) {
1213
+ var i18n = this.options.i18n;
1214
+ var day = this.date.getDay();
1215
+ var month = this.date.getMonth();
1216
+ var date = this.date.getDate();
1217
+ var year = this.date.getFullYear();
1218
+ var hour = this.date.getHours();
1219
+ var minute = this.date.getMinutes();
1220
+ var second = this.date.getSeconds();
1221
+
1222
+ var hour_ampm = (hour == 0 ? 12 : hour < 13 ? hour : hour - 12);
1223
+
1224
+ var values = {
1225
+ a: i18n.dayNamesShort[day],
1226
+ A: i18n.dayNames[day],
1227
+ b: i18n.monthNamesShort[month],
1228
+ B: i18n.monthNames[month],
1229
+ d: zerofy(date),
1230
+ e: ''+date,
1231
+ m: (month < 9 ? '0' : '') + (month+1),
1232
+ y: (''+year).substring(2,4),
1233
+ Y: ''+year,
1234
+ H: zerofy(hour),
1235
+ k: '' + hour,
1236
+ I: (hour > 0 && (hour < 10 || (hour > 12 && hour < 22)) ? '0' : '') + hour_ampm,
1237
+ l: '' + hour_ampm,
1238
+ p: hour < 12 ? 'AM' : 'PM',
1239
+ P: hour < 12 ? 'am' : 'pm',
1240
+ M: zerofy(minute),
1241
+ S: zerofy(second),
1242
+ '%': '%'
1243
+ };
1244
+
1245
+ var result = format || this.options.format;
1246
+ for (var key in values) {
1247
+ result = result.replace('%'+key, values[key]);
1248
+ }
1249
+
1250
+ return result;
1251
+ }
1252
+ });
1253
+
1254
+
1255
+ /**
1256
+ * This module handles the events connection
1257
+ *
1258
+ * Copyright (C) 2009-2010 Nikolay Nemshilov
1259
+ */
1260
+ Calendar.include({
1261
+
1262
+ // protected
1263
+
1264
+ // connects the events with handlers
1265
+ initEvents: function() {
1266
+ var shift = '_shiftDate', terminate = this._terminate;
1267
+
1268
+ this.on({
1269
+ // the dates/months/etc listing events
1270
+ 'prev-day': [shift, {Date: -1}],
1271
+ 'next-day': [shift, {Date: 1}],
1272
+ 'prev-week': [shift, {Date: -7}],
1273
+ 'next-week': [shift, {Date: 7}],
1274
+ 'prev-month': [shift, {Month: -1}],
1275
+ 'next-month': [shift, {Month: 1}],
1276
+ 'prev-year': [shift, {FullYear: -1}],
1277
+ 'next-year': [shift, {FullYear: 1}],
1278
+
1279
+ // the date/time picking events
1280
+ 'date-set': this._changeDate,
1281
+ 'time-set': this._changeTime,
1282
+
1283
+ // the bottom buttons events
1284
+ 'now-clicked': this._setNow,
1285
+ 'done-clicked': this.done,
1286
+
1287
+ // handling the clicks
1288
+ 'click': terminate,
1289
+ 'mousedown': terminate,
1290
+ 'focus': terminate,
1291
+ 'blur': terminate
1292
+ });
1293
+ },
1294
+
1295
+ // shifts the date according to the params
1296
+ _shiftDate: function(params) {
1297
+ var date = new Date(this.date), options = this.options;
1298
+
1299
+ // shifting the date according to the params
1300
+ for (var key in params) {
1301
+ date['set'+key](date['get'+key]() + params[key]);
1302
+ }
1303
+
1304
+ this.setDate(date);
1305
+ },
1306
+
1307
+ // changes the current date (not the time)
1308
+ _changeDate: function(event) {
1309
+ var date = new Date(this.date);
1310
+
1311
+ date.setDate(event.date);
1312
+ date.setMonth(event.month);
1313
+ date.setFullYear(event.year);
1314
+
1315
+ this.setDate(date, true); // <- `true` means just change the date without shifting the list
1316
+
1317
+ if (this.options.hideOnPick) {
1318
+ this.done();
1319
+ }
1320
+ },
1321
+
1322
+ // changes the current time (not the date)
1323
+ _changeTime: function(event) {
1324
+ var date = new Date(this.date);
1325
+
1326
+ date.setHours(event.hours);
1327
+ date.setMinutes(event.minutes);
1328
+
1329
+ this.setDate(date);
1330
+ },
1331
+
1332
+ // resets the calendar to the current time
1333
+ _setNow: function() {
1334
+ this.setDate(new Date());
1335
+ },
1336
+
1337
+ /** simply stops the event so we didn't bother the things outside of the object
1338
+ *
1339
+ * @param {Event} event
1340
+ * @return void
1341
+ * @private
1342
+ */
1343
+ _terminate: function(event) {
1344
+ event.stopPropagation(); // don't let the clicks go anywere out of the clanedar
1345
+
1346
+ if (this._hide_delay) {
1347
+ this._hide_delay.cancel();
1348
+ this._hide_delay = null;
1349
+ }
1350
+ }
1351
+ });
1352
+
1353
+
1354
+ /**
1355
+ * Document level event listeners for navigation and lazy initialization
1356
+ *
1357
+ * Copyright (C) 2009-2010 Nikolay Nemshilov
1358
+ */
1359
+ $(document).on({
1360
+ /**
1361
+ * Watches the focus events and dispalys the calendar
1362
+ * popups when there is a related input element
1363
+ *
1364
+ * @param Event focus-event
1365
+ * @return void
1366
+ */
1367
+ focus: function(event) {
1368
+ var target = event.target instanceof Input && event.target.get('type') == 'text' ? event.target : null;
1369
+
1370
+ Calendar.hideAll();
1371
+
1372
+ if (target && (target.calendar || target.match(Calendar.Options.cssRule))) {
1373
+ (target.calendar || new Calendar({update: target}))
1374
+ .setValue(target.value()).showAt(target);
1375
+ }
1376
+ },
1377
+
1378
+ /**
1379
+ * Watches the input elements blur events
1380
+ * and hides shown popups
1381
+ *
1382
+ * @param Event blur-event
1383
+ * @return void
1384
+ */
1385
+ blur: function(event) {
1386
+ var target = event.target, calendar = target.calendar;
1387
+
1388
+ if (calendar) {
1389
+ // we use the delay so it didn't get hidden when the user clicks the calendar itself
1390
+ calendar._hide_delay = R(function() {
1391
+ calendar.hide();
1392
+ }).delay(200);
1393
+ }
1394
+ },
1395
+
1396
+ /**
1397
+ * Catches clicks on trigger elements
1398
+ *
1399
+ * @param Event click
1400
+ * @return void
1401
+ */
1402
+ click: function(event) {
1403
+ var target = (event.target instanceof Element) ? event.target : null;
1404
+
1405
+ if (target && (target.calendar || target.match(Calendar.Options.cssRule))) {
1406
+ if (!(target instanceof Input) || target.get('type') != 'text') {
1407
+ event.stop();
1408
+ (target.calendar || new Calendar({trigger: target}))
1409
+ .hide(null).toggleAt(target.assignedInput);
1410
+ }
1411
+ } else if (!event.find('div.rui-calendar')){
1412
+ Calendar.hideAll();
1413
+ }
1414
+ },
1415
+
1416
+ /**
1417
+ * Catching the key-downs to navigate in the currently
1418
+ * opened Calendar hover
1419
+ *
1420
+ * @param Event event
1421
+ * @return void
1422
+ */
1423
+ keydown: function(event) {
1424
+ var calendar = Calendar.current, name = ({
1425
+ 27: 'hide', // Escape
1426
+ 37: 'prev-day', // Left Arrow
1427
+ 39: 'next-day', // Right Arrow
1428
+ 38: 'prev-week', // Up Arrow
1429
+ 40: 'next-week', // Down Arrow
1430
+ 33: 'prev-month', // Page Up
1431
+ 34: 'next-month', // Page Down
1432
+ 13: 'done' // Enter
1433
+ })[event.keyCode];
1434
+
1435
+ if (name && calendar && calendar.visible()) {
1436
+ event.stop();
1437
+ if (isFunction(calendar[name])) {
1438
+ calendar[name]();
1439
+ } else {
1440
+ calendar.fire(name);
1441
+ }
1442
+ }
1443
+ }
1444
+ });
1445
+ (function() {
1446
+ var style = document.createElement('style'),
1447
+ rules = document.createTextNode(".rui-panel{margin:0;padding:.5em;position:relative;background-color:#EEE;border:1px solid #BBB;border-radius:.3em;-moz-border-radius:.3em;-webkit-border-radius:.3em;box-shadow:.15em .3em .5em #BBB;-moz-box-shadow:.15em .3em .5em #BBB;-webkit-box-shadow:.15em .3em .5em #BBB;cursor:default} *.rui-button{display:inline-block; *display:inline; *zoom:1;height:1em;line-height:1em;margin:0;padding:.2em .5em;text-align:center;border:1px solid #CCC;border-radius:.2em;-moz-border-radius:.2em;-webkit-border-radius:.2em;cursor:pointer;color:#333;background-color:#FFF;user-select:none;-moz-user-select:none;-webkit-user-select:none} *.rui-button:hover{color:#111;border-color:#999;background-color:#DDD;box-shadow:#888 0 0 .1em;-moz-box-shadow:#888 0 0 .1em;-webkit-box-shadow:#888 0 0 .1em} *.rui-button:active{color:#000;border-color:#777;text-indent:1px;box-shadow:none;-moz-box-shadow:none;-webkit-box-shadow:none} *.rui-button-disabled, *.rui-button-disabled:hover, *.rui-button-disabled:active{color:#888;background:#DDD;border-color:#CCC;cursor:default;text-indent:0;box-shadow:none;-moz-box-shadow:none;-webkit-box-shadow:none}div.rui-re-anchor{margin:0;padding:0;background:none;border:none;float:none;display:inline;position:absolute;z-index:9999}div.rui-calendar .swaps,div.rui-calendar .greed,div.rui-calendar .timepicker,div.rui-calendar .buttons,div.rui-calendar table,div.rui-calendar table tr,div.rui-calendar table th,div.rui-calendar table td,div.rui-calendar table tbody,div.rui-calendar table thead,div.rui-calendar table caption{background:none;border:none;width:auto;height:auto;margin:0;padding:0}div.rui-calendar-inline{position:relative;display:inline-block; *display:inline; *zoom:1;box-shadow:none;-moz-box-shadow:none;-webkit-box-shadow:none}div.rui-calendar .swaps{position:relative}div.rui-calendar .swaps .rui-button{position:absolute;float:left;width:1em;padding:.15em .4em}div.rui-calendar .swaps .next-month{right:0em;_right:.5em}div.rui-calendar .swaps .prev-year{left:2.05em}div.rui-calendar .swaps .next-year{right:2.05em;_right:2.52em}div.rui-calendar .greed{border-spacing:0px;border-collapse:collapse;border-size:0}div.rui-calendar .greed td{vertical-align:top;padding-left:.4em}div.rui-calendar .greed>tbody>tr>td:first-child{padding:0}div.rui-calendar .month{margin-top:.2em;border-spacing:1px;border-collapse:separate}div.rui-calendar .month caption{text-align:center}div.rui-calendar .month th{color:#666;text-align:center}div.rui-calendar .month td{text-align:right;padding:.1em .3em;background-color:#FFF;border:1px solid #CCC;cursor:pointer;color:#555;border-radius:.2em;-moz-border-radius:.2em;-webkit-border-radius:.2em}div.rui-calendar .month td:hover{background-color:#CCC;border-color:#AAA;color:#000}div.rui-calendar .month td.blank{background:transparent;cursor:default;border:none}div.rui-calendar .month td.selected{background-color:#BBB;border-color:#AAA;color:#222;font-weight:bold;padding:.1em .2em}div.rui-calendar .month td.disabled{color:#888;background:#EEE;border-color:#CCC;cursor:default}div.rui-calendar .timepicker{border-top:1px solid #ccc;margin-top:.3em;padding-top:.5em;text-align:center}div.rui-calendar .timepicker select{margin:0 .4em}div.rui-calendar .buttons{position:relative;margin-top:.5em}div.rui-calendar .buttons div.rui-button{width:4em;padding:.25em .5em}div.rui-calendar .buttons .done{position:absolute;right:0em;top:0}");
1448
+
1449
+ style.type = 'text/css';
1450
+
1451
+ if(style.styleSheet) {
1452
+ style.styleSheet.cssText = rules.nodeValue;
1453
+ } else {
1454
+ style.appendChild(rules);
1455
+ }
1456
+
1457
+ document.getElementsByTagName('head')[0].appendChild(style);
1458
+ })();
1459
+
1460
+ return Calendar;
1461
+ })(document, parseInt, RightJS);